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:
parent
5409f4adbe
commit
a21a32ccfa
@ -38,6 +38,7 @@ APPCONFIGS = {}
|
||||
def setup_app(root, conf=None):
|
||||
app_hooks = [hooks.ConfigHook(conf),
|
||||
hooks.TranslationHook(),
|
||||
hooks.GCHook(),
|
||||
hooks.RPCHook(conf),
|
||||
hooks.ContextHook(),
|
||||
hooks.DBHook(conf)]
|
||||
|
@ -13,7 +13,6 @@
|
||||
# under the License.
|
||||
|
||||
|
||||
import json
|
||||
from oslo_log import log
|
||||
from oslo_utils.strutils import bool_from_string
|
||||
import pecan
|
||||
@ -23,7 +22,7 @@ from vitrage.api.controllers.rest import RootRestController
|
||||
from vitrage.api.policy import enforce
|
||||
from vitrage.common.constants import TenantProps
|
||||
from vitrage.common.constants import VertexProperties as Vprops
|
||||
|
||||
from vitrage.common.utils import decompress_obj
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
@ -53,7 +52,7 @@ class BaseAlarmsController(RootRestController):
|
||||
enforce("list alarms", pecan.request.headers,
|
||||
pecan.request.enforcer, {})
|
||||
|
||||
alarms_json = \
|
||||
alarms = \
|
||||
pecan.request.client.call(pecan.request.context,
|
||||
'get_alarms',
|
||||
vitrage_id=vitrage_id,
|
||||
@ -71,7 +70,7 @@ class BaseAlarmsController(RootRestController):
|
||||
)
|
||||
|
||||
try:
|
||||
alarms_list = json.loads(alarms_json)['alarms']
|
||||
alarms_list = decompress_obj(alarms)['alarms']
|
||||
return alarms_list
|
||||
|
||||
except Exception:
|
||||
|
@ -19,7 +19,7 @@ from pecan.core import abort
|
||||
|
||||
from vitrage.api.controllers.rest import RootRestController
|
||||
from vitrage.api.policy import enforce
|
||||
|
||||
from vitrage.common.utils import decompress_obj
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
@ -55,13 +55,12 @@ class ResourcesController(RootRestController):
|
||||
LOG.info('get_resources with type: %s, all_tenants: %s',
|
||||
resource_type, all_tenants)
|
||||
try:
|
||||
resources_json = \
|
||||
resources = \
|
||||
pecan.request.client.call(pecan.request.context,
|
||||
'get_resources',
|
||||
resource_type=resource_type,
|
||||
all_tenants=all_tenants)
|
||||
LOG.info(resources_json)
|
||||
resources = json.loads(resources_json)['resources']
|
||||
resources = decompress_obj(resources)['resources']
|
||||
return resources
|
||||
except Exception:
|
||||
LOG.exception('Failed to get resources.')
|
||||
|
@ -9,6 +9,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.
|
||||
import gc
|
||||
|
||||
from vitrage.api.controllers.v1 import alarm
|
||||
from vitrage.api.controllers.v1 import event
|
||||
@ -20,6 +21,9 @@ from vitrage.api.controllers.v1 import webhook
|
||||
|
||||
|
||||
class V1Controller(object):
|
||||
|
||||
gc.set_threshold(1, 1, 1)
|
||||
|
||||
topology = topology.TopologyController()
|
||||
resources = resource.ResourcesController()
|
||||
alarm = alarm.AlarmsController()
|
||||
|
@ -12,7 +12,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import json
|
||||
import networkx as nx
|
||||
|
||||
@ -27,6 +26,7 @@ from vitrage.api.policy import enforce
|
||||
from vitrage.common.constants import VertexProperties as VProps
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
from vitrage.common.utils import decompress_obj
|
||||
from vitrage.datasources.transformer_base import CLUSTER_ID
|
||||
|
||||
|
||||
@ -77,8 +77,7 @@ class TopologyController(RootRestController):
|
||||
query=query,
|
||||
root=root,
|
||||
all_tenants=all_tenants)
|
||||
LOG.debug(graph_data)
|
||||
graph = json.loads(graph_data)
|
||||
graph = decompress_obj(graph_data)
|
||||
if graph_type == 'graph':
|
||||
return graph
|
||||
if graph_type == 'tree':
|
||||
|
@ -9,7 +9,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.
|
||||
|
||||
import gc
|
||||
import oslo_messaging
|
||||
|
||||
from oslo_context import context
|
||||
@ -89,3 +89,9 @@ class DBHook(hooks.PecanHook):
|
||||
|
||||
def before(self, state):
|
||||
state.request.storage = self.storage
|
||||
|
||||
|
||||
class GCHook(hooks.PecanHook):
|
||||
|
||||
def after(self, state):
|
||||
gc.collect()
|
||||
|
@ -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 TenantProps
|
||||
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.entity_graph.mappings.operational_alarm_severity import \
|
||||
OperationalAlarmSeverity
|
||||
@ -54,7 +55,8 @@ class AlarmApis(EntityGraphApisBase):
|
||||
kwargs.get('filter_vals', []).append(vitrage_id)
|
||||
|
||||
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
|
||||
def show_alarm(self, ctx, vitrage_id):
|
||||
|
@ -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 TenantProps
|
||||
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__)
|
||||
|
||||
@ -34,6 +35,7 @@ class ResourceApis(EntityGraphApisBase):
|
||||
self.entity_graph = entity_graph
|
||||
self.conf = conf
|
||||
|
||||
@timed_method(log_results=True)
|
||||
def get_resources(self, ctx, resource_type=None, all_tenants=False):
|
||||
LOG.debug('ResourceApis get_resources - resource_type: %s,'
|
||||
'all_tenants: %s', str(resource_type), all_tenants)
|
||||
@ -55,8 +57,8 @@ class ResourceApis(EntityGraphApisBase):
|
||||
query['and'].append(type_query)
|
||||
|
||||
resources = self.entity_graph.get_vertices(query_dict=query)
|
||||
return json.dumps({'resources': [resource.properties
|
||||
for resource in resources]})
|
||||
data = {'resources': [r.properties for r in resources]}
|
||||
return compress_obj(data, level=1)
|
||||
|
||||
def show_resource(self, ctx, vitrage_id):
|
||||
|
||||
|
@ -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 networkx.algorithms.shortest_paths.generic import shortest_path
|
||||
|
||||
from oslo_log import log
|
||||
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 VertexProperties as VProps
|
||||
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
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
@ -38,13 +40,13 @@ class TopologyApis(EntityGraphApisBase):
|
||||
self.entity_graph = entity_graph
|
||||
self.conf = conf
|
||||
|
||||
@timed_method(log_results=True)
|
||||
def get_topology(self, ctx, graph_type, depth, query, root, all_tenants):
|
||||
LOG.debug("TopologyApis get_topology - root: %s, all_tenants=%s",
|
||||
str(root), all_tenants)
|
||||
|
||||
project_id = ctx.get(TenantProps.TENANT, None)
|
||||
is_admin_project = ctx.get(TenantProps.IS_ADMIN, False)
|
||||
ga = self.entity_graph.algo
|
||||
|
||||
LOG.debug('project_id = %s, is_admin_project %s',
|
||||
project_id, is_admin_project)
|
||||
@ -63,29 +65,30 @@ class TopologyApis(EntityGraphApisBase):
|
||||
{'==': {VProps.PROJECT_ID: None}}]}
|
||||
current_query = {'and': [query, project_query]}
|
||||
|
||||
graph = ga.graph_query_vertices(root_id,
|
||||
query_dict=current_query,
|
||||
depth=depth,
|
||||
edge_query_dict=EDGE_QUERY)
|
||||
graph = self.entity_graph.algo.graph_query_vertices(
|
||||
root_id,
|
||||
query_dict=current_query,
|
||||
depth=depth,
|
||||
edge_query_dict=EDGE_QUERY)
|
||||
# By default the graph_type is 'graph'
|
||||
else:
|
||||
if all_tenants:
|
||||
q = query if query else TOPOLOGY_AND_ALARMS_QUERY
|
||||
graph = ga.create_graph_from_matching_vertices(
|
||||
query_dict=q,
|
||||
edge_attr_filter={VProps.VITRAGE_IS_DELETED: False})
|
||||
graph = \
|
||||
self.entity_graph.algo.create_graph_from_matching_vertices(
|
||||
query_dict=q,
|
||||
edge_attr_filter={VProps.VITRAGE_IS_DELETED: False})
|
||||
else:
|
||||
graph = self._get_topology_for_specific_project(
|
||||
ga,
|
||||
query,
|
||||
project_id,
|
||||
is_admin_project,
|
||||
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,
|
||||
ga,
|
||||
query,
|
||||
project_id,
|
||||
is_admin_project,
|
||||
@ -95,7 +98,6 @@ class TopologyApis(EntityGraphApisBase):
|
||||
Finds all the entities which has project_id. In case the tenant is
|
||||
admin then project_id can also be None.
|
||||
|
||||
:type ga: NXAlgorithm
|
||||
:type query: dictionary
|
||||
:type project_id: string
|
||||
:type is_admin_project: boolean
|
||||
@ -118,25 +120,24 @@ class TopologyApis(EntityGraphApisBase):
|
||||
default_query = {'or': [resource_query, alarm_query]}
|
||||
q = default_query
|
||||
|
||||
tmp_graph = ga.create_graph_from_matching_vertices(query_dict=q)
|
||||
graph = self._create_graph_of_connected_components(ga, tmp_graph, root)
|
||||
vertices_ids = self.entity_graph.get_vertices_ids(query_dict=q)
|
||||
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}
|
||||
self._remove_unnecessary_elements(ga,
|
||||
graph,
|
||||
project_id,
|
||||
is_admin_project,
|
||||
edge_attr_filter=edge_query)
|
||||
self._remove_unnecessary_elements(
|
||||
graph, project_id, is_admin_project, edge_attr_filter=edge_query)
|
||||
|
||||
return graph
|
||||
|
||||
def _remove_unnecessary_elements(self,
|
||||
ga,
|
||||
graph,
|
||||
project_id,
|
||||
is_admin_project,
|
||||
edge_attr_filter):
|
||||
# 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,
|
||||
project_id,
|
||||
@ -173,44 +174,19 @@ class TopologyApis(EntityGraphApisBase):
|
||||
if cond1 or cond2:
|
||||
graph.remove_vertex(alarm)
|
||||
|
||||
def _create_graph_of_connected_components(self, ga, tmp_graph, root):
|
||||
return ga.subgraph(self._topology_for_unrooted_graph(ga,
|
||||
tmp_graph,
|
||||
root)).copy()
|
||||
@staticmethod
|
||||
def _all_paths_from_node(graph, source_node, targets):
|
||||
"""Find all nodes on a (shortest) path from source to targets
|
||||
|
||||
def _topology_for_unrooted_graph(self, ga, subgraph, root):
|
||||
"""Finds topology for unrooted subgraph
|
||||
Return all the node ids that are either in targets
|
||||
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
|
||||
"""
|
||||
|
||||
entities = []
|
||||
|
||||
root_vertex = \
|
||||
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)
|
||||
vertices_ids = targets
|
||||
paths = shortest_path(graph._g, source=source_node)
|
||||
vertices_ids.update(*[set(paths.get(n, [])) for n in targets])
|
||||
return vertices_ids
|
||||
|
||||
def _default_root_id(self):
|
||||
tmp_vertices = self.entity_graph.get_vertices(
|
||||
@ -221,13 +197,3 @@ class TopologyApis(EntityGraphApisBase):
|
||||
if len(tmp_vertices) > 1:
|
||||
raise VitrageError("Multiple root vertices found")
|
||||
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
|
||||
|
@ -16,18 +16,25 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import base64
|
||||
from collections import defaultdict
|
||||
import copy
|
||||
import hashlib
|
||||
import itertools
|
||||
import random
|
||||
import six
|
||||
from six.moves import cPickle
|
||||
import threading
|
||||
import time
|
||||
import zlib
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
import cProfile
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def recursive_keypairs(d, separator='.'):
|
||||
# taken from ceilometer and gnocchi
|
||||
@ -116,3 +123,35 @@ def fmt(docstr):
|
||||
docstr = docstr.strip()
|
||||
|
||||
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
|
||||
|
@ -79,29 +79,8 @@ class GraphAlgorithm(object):
|
||||
"""
|
||||
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
|
||||
def create_graph_from_matching_vertices(self,
|
||||
vertex_attr_filter=None,
|
||||
query_dict=None,
|
||||
edge_attr_filter=None):
|
||||
"""Generate graph using the query
|
||||
@ -109,7 +88,6 @@ class GraphAlgorithm(object):
|
||||
Finds all the vertices in the graph matching the query, and returns
|
||||
a subgraph consisted from the vertices
|
||||
|
||||
:type vertex_attr_filter: dictionary
|
||||
:type query_dict: dictionary
|
||||
:type edge_attr_filter: dictionary
|
||||
:rtype: NXGraph
|
||||
|
@ -12,9 +12,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from networkx.algorithms import components
|
||||
from networkx.algorithms import simple_paths
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
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 subgraph_matching
|
||||
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.query import create_predicate
|
||||
|
||||
@ -84,10 +79,11 @@ class NXAlgorithm(GraphAlgorithm):
|
||||
e_result.extend(e_list)
|
||||
nodes_q.extend([(v_id, curr_depth + 1) for v_id, data in n_list])
|
||||
|
||||
graph = self._create_new_graph(
|
||||
graph.name,
|
||||
vertices=self._vertex_result_to_list(n_result),
|
||||
edges=self._edge_result_to_list(e_result))
|
||||
graph = self._create_new_graph(graph.name)
|
||||
for v_id, data in n_result:
|
||||
graph._g.add_node(v_id, **data)
|
||||
for source_id, target_id, label, data in e_result:
|
||||
graph._g.add_edge(source_id, target_id, label, **data)
|
||||
|
||||
return graph
|
||||
|
||||
@ -128,32 +124,15 @@ class NXAlgorithm(GraphAlgorithm):
|
||||
validate)
|
||||
|
||||
def create_graph_from_matching_vertices(self,
|
||||
vertex_attr_filter=None,
|
||||
query_dict=None,
|
||||
edge_attr_filter=None):
|
||||
if 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]
|
||||
|
||||
vertices_ids = self.graph.get_vertices_ids(query_dict=query_dict)
|
||||
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
|
||||
if 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))))
|
||||
self.apply_edge_attr_filter(graph, edge_attr_filter)
|
||||
|
||||
return graph
|
||||
|
||||
@ -162,15 +141,6 @@ class NXAlgorithm(GraphAlgorithm):
|
||||
subgraph._g = self.graph._g.subgraph(entities)
|
||||
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,
|
||||
ge_v_id,
|
||||
sge_v_id,
|
||||
@ -191,21 +161,6 @@ class NXAlgorithm(GraphAlgorithm):
|
||||
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def _edge_result_to_list(edge_result):
|
||||
d = dict()
|
||||
for source_id, target_id, label, data in edge_result:
|
||||
d[(source_id, target_id, label)] = \
|
||||
Edge(source_id, target_id, label, properties=data)
|
||||
return d.values()
|
||||
|
||||
@staticmethod
|
||||
def _vertex_result_to_list(vertex_result):
|
||||
d = dict()
|
||||
for v_id, data in vertex_result:
|
||||
d[v_id] = Vertex(vertex_id=v_id, properties=data)
|
||||
return d.values()
|
||||
|
||||
@staticmethod
|
||||
def _list_union(list_1, list_2):
|
||||
"""Union of list that aren't hashable
|
||||
@ -223,7 +178,7 @@ class NXAlgorithm(GraphAlgorithm):
|
||||
return list_1
|
||||
|
||||
@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_to_remove = [(u, v, k) for (u, v, k, d) in edges
|
||||
if not check_filter(d, edge_attr_filter)]
|
||||
|
@ -250,6 +250,17 @@ class NXGraph(Graph):
|
||||
else:
|
||||
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):
|
||||
|
||||
if key_values_hash in self.key_to_vertex_ids:
|
||||
|
@ -18,6 +18,7 @@
|
||||
from datetime import datetime
|
||||
# noinspection PyPackageRequirements
|
||||
from mock import mock
|
||||
from vitrage.common.utils import compress_obj
|
||||
|
||||
from vitrage.storage.sqlalchemy import models
|
||||
from vitrage.tests.functional.api.v1 import FunctionalTest
|
||||
@ -54,7 +55,7 @@ class NoAuthTest(FunctionalTest):
|
||||
|
||||
def test_noauth_mode_get_topology(self):
|
||||
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,
|
||||
root=None,
|
||||
all_tenants=False)
|
||||
@ -66,7 +67,7 @@ class NoAuthTest(FunctionalTest):
|
||||
|
||||
def test_noauth_mode_list_alarms(self):
|
||||
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)
|
||||
data = self.get_json('/alarm/', params=params)
|
||||
|
||||
@ -95,7 +96,7 @@ class NoAuthTest(FunctionalTest):
|
||||
def test_noauth_mode_list_resources(self):
|
||||
|
||||
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)
|
||||
data = self.get_json('/resources/', params=params)
|
||||
|
||||
|
@ -26,6 +26,7 @@ from vitrage.common.constants import EdgeLabel
|
||||
from vitrage.common.constants import EdgeProperties
|
||||
from vitrage.common.constants import EntityCategory
|
||||
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_INSTANCE_DATASOURCE
|
||||
from vitrage.datasources import NOVA_ZONE_DATASOURCE
|
||||
@ -60,7 +61,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
|
||||
|
||||
# Action
|
||||
alarms = apis.get_alarms(ctx, vitrage_id='all', all_tenants=False)
|
||||
alarms = json.loads(alarms)['alarms']
|
||||
alarms = decompress_obj(alarms)['alarms']
|
||||
|
||||
# Test assertions
|
||||
self.assertThat(alarms, matchers.HasLength(3))
|
||||
@ -74,7 +75,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
|
||||
|
||||
# Action
|
||||
alarms = apis.get_alarms(ctx, vitrage_id='all', all_tenants=False)
|
||||
alarms = json.loads(alarms)['alarms']
|
||||
alarms = decompress_obj(alarms)['alarms']
|
||||
|
||||
# Test assertions
|
||||
self.assertThat(alarms, matchers.HasLength(2))
|
||||
@ -105,7 +106,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
|
||||
|
||||
# Action
|
||||
alarms = apis.get_alarms(ctx, vitrage_id='all', all_tenants=True)
|
||||
alarms = json.loads(alarms)['alarms']
|
||||
alarms = decompress_obj(alarms)['alarms']
|
||||
|
||||
# Test assertions
|
||||
self.assertThat(alarms, matchers.HasLength(5))
|
||||
@ -200,7 +201,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
|
||||
query=None,
|
||||
root=None,
|
||||
all_tenants=False)
|
||||
graph_topology = json.loads(graph_topology)
|
||||
graph_topology = decompress_obj(graph_topology)
|
||||
|
||||
# Test assertions
|
||||
self.assertThat(graph_topology['nodes'], matchers.HasLength(8))
|
||||
@ -222,7 +223,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
|
||||
query=None,
|
||||
root=None,
|
||||
all_tenants=False)
|
||||
graph_topology = json.loads(graph_topology)
|
||||
graph_topology = decompress_obj(graph_topology)
|
||||
|
||||
# Test assertions
|
||||
self.assertThat(graph_topology['nodes'], matchers.HasLength(7))
|
||||
@ -244,7 +245,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
|
||||
query=None,
|
||||
root=None,
|
||||
all_tenants=True)
|
||||
graph_topology = json.loads(graph_topology)
|
||||
graph_topology = decompress_obj(graph_topology)
|
||||
|
||||
# Test assertions
|
||||
self.assertThat(graph_topology['nodes'], matchers.HasLength(12))
|
||||
@ -260,7 +261,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
|
||||
ctx,
|
||||
resource_type=None,
|
||||
all_tenants=False)
|
||||
resources = json.loads(resources)['resources']
|
||||
resources = decompress_obj(resources)['resources']
|
||||
|
||||
# Test assertions
|
||||
self.assertThat(resources, matchers.HasLength(5))
|
||||
@ -276,7 +277,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
|
||||
ctx,
|
||||
resource_type=None,
|
||||
all_tenants=False)
|
||||
resources = json.loads(resources)['resources']
|
||||
resources = decompress_obj(resources)['resources']
|
||||
|
||||
# Test assertions
|
||||
self.assertThat(resources, matchers.HasLength(2))
|
||||
@ -292,7 +293,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
|
||||
ctx,
|
||||
resource_type=NOVA_HOST_DATASOURCE,
|
||||
all_tenants=False)
|
||||
resources = json.loads(resources)['resources']
|
||||
resources = decompress_obj(resources)['resources']
|
||||
|
||||
# Test assertions
|
||||
self.assertThat(resources, IsEmpty())
|
||||
@ -308,7 +309,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
|
||||
ctx,
|
||||
resource_type=NOVA_INSTANCE_DATASOURCE,
|
||||
all_tenants=False)
|
||||
resources = json.loads(resources)['resources']
|
||||
resources = decompress_obj(resources)['resources']
|
||||
|
||||
# Test assertions
|
||||
self.assertThat(resources, matchers.HasLength(2))
|
||||
@ -324,7 +325,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
|
||||
ctx,
|
||||
resource_type=None,
|
||||
all_tenants=True)
|
||||
resources = json.loads(resources)['resources']
|
||||
resources = decompress_obj(resources)['resources']
|
||||
|
||||
# Test assertions
|
||||
self.assertThat(resources, matchers.HasLength(7))
|
||||
|
Loading…
Reference in New Issue
Block a user