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):
|
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)]
|
||||||
|
@ -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:
|
||||||
|
@ -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.')
|
||||||
|
@ -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()
|
||||||
|
@ -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':
|
||||||
|
@ -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()
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
|
||||||
|
@ -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,29 +65,30 @@ 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(
|
||||||
query_dict=current_query,
|
root_id,
|
||||||
depth=depth,
|
query_dict=current_query,
|
||||||
edge_query_dict=EDGE_QUERY)
|
depth=depth,
|
||||||
|
edge_query_dict=EDGE_QUERY)
|
||||||
# By default the graph_type is 'graph'
|
# By default the graph_type is 'graph'
|
||||||
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 = \
|
||||||
query_dict=q,
|
self.entity_graph.algo.create_graph_from_matching_vertices(
|
||||||
edge_attr_filter={VProps.VITRAGE_IS_DELETED: False})
|
query_dict=q,
|
||||||
|
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
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)]
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
Loading…
Reference in New Issue
Block a user