Multi tenancy for topology, alarms and rca apis.

Change-Id: I2ce82e755d22784df1ddefabef738a27b7c2316f
This commit is contained in:
Alexey Weyl 2016-10-26 15:30:08 +03:00
parent 42cf7d3790
commit 4f06e3a515
29 changed files with 1320 additions and 344 deletions

View File

@ -135,6 +135,7 @@ Consists of a topology request definition which has the following properties:
* depth - (int, optional) the depth of the topology graph. defaults to max depth * depth - (int, optional) the depth of the topology graph. defaults to max depth
* graph_type-(string, optional) can be either tree or graph. defaults to graph * graph_type-(string, optional) can be either tree or graph. defaults to graph
* query - (string, optional) a json query filter to filter the graph components. defaults to return all the graph * query - (string, optional) a json query filter to filter the graph components. defaults to return all the graph
* all_tenants -
query expression query expression
================ ================

View File

@ -1,10 +1,13 @@
{ {
"get topology": "role:admin", "get topology": "",
"get resource": "role:admin", "get topology:all_tenants": "role:admin",
"list resources": "role:admin", "get resource": "",
"list alarms": "role:admin", "list resources": "",
"get rca": "role:admin", "list alarms": "",
"template validate": "role:admin", "list alarms:all_tenants": "role:admin",
"template list": "role:admin", "get rca": "",
"template show": "role:admin" "get rca:all_tenants": "role:admin",
"template validate": "",
"template list": "",
"template show": ""
} }

View File

@ -30,13 +30,17 @@ LOG = log.getLogger(__name__)
class AlarmsController(RootRestController): class AlarmsController(RootRestController):
@pecan.expose('json') @pecan.expose('json')
def index(self, vitrage_id=None): def index(self, vitrage_id, all_tenants='0'):
return self.post(vitrage_id) return self.post(vitrage_id, all_tenants)
@pecan.expose('json') @pecan.expose('json')
def post(self, vitrage_id): def post(self, vitrage_id, all_tenants='0'):
enforce("list alarms", pecan.request.headers, if all_tenants == '1':
pecan.request.enforcer, {}) enforce("list alarms:all_tenants", pecan.request.headers,
pecan.request.enforcer, {})
else:
enforce("list alarms", pecan.request.headers,
pecan.request.enforcer, {})
LOG.info(_LI('returns list alarms with vitrage id %s') % LOG.info(_LI('returns list alarms with vitrage id %s') %
vitrage_id) vitrage_id)
@ -45,16 +49,17 @@ class AlarmsController(RootRestController):
if pecan.request.cfg.api.use_mock_file: if pecan.request.cfg.api.use_mock_file:
return self.get_mock_data('alarms.sample.json') return self.get_mock_data('alarms.sample.json')
else: else:
return self._get_alarms(vitrage_id) return self._get_alarms(vitrage_id, all_tenants)
except Exception as e: except Exception as e:
LOG.exception('failed to get alarms %s', e) LOG.exception('failed to get alarms %s', e)
abort(404, str(e)) abort(404, str(e))
@staticmethod @staticmethod
def _get_alarms(vitrage_id=None): def _get_alarms(vitrage_id=None, all_tenants=0):
alarms_json = pecan.request.client.call(pecan.request.context, alarms_json = pecan.request.client.call(pecan.request.context,
'get_alarms', 'get_alarms',
arg=vitrage_id) vitrage_id=vitrage_id,
all_tenants=all_tenants)
LOG.info(alarms_json) LOG.info(alarms_json)
try: try:

View File

@ -30,26 +30,31 @@ LOG = log.getLogger(__name__)
class RCAController(RootRestController): class RCAController(RootRestController):
@pecan.expose('json') @pecan.expose('json')
def index(self, alarm_id): def index(self, alarm_id, all_tenants='0'):
return self.get(alarm_id) return self.get(alarm_id, all_tenants)
@pecan.expose('json') @pecan.expose('json')
def get(self, alarm_id): def get(self, alarm_id, all_tenants='0'):
enforce('get rca', pecan.request.headers, if all_tenants == '1':
pecan.request.enforcer, {}) enforce('get rca:all_tenants', pecan.request.headers,
pecan.request.enforcer, {})
else:
enforce('get rca', pecan.request.headers,
pecan.request.enforcer, {})
LOG.info(_LI('received show rca with alarm id %s') % alarm_id) LOG.info(_LI('received show rca with alarm id %s') % alarm_id)
if pecan.request.cfg.api.use_mock_file: if pecan.request.cfg.api.use_mock_file:
return self.get_mock_data('rca.sample.json') return self.get_mock_data('rca.sample.json')
else: else:
return self.get_rca(alarm_id) return self.get_rca(alarm_id, all_tenants)
@staticmethod @staticmethod
def get_rca(alarm_id): def get_rca(alarm_id, all_tenants):
try: try:
graph_data = pecan.request.client.call(pecan.request.context, graph_data = pecan.request.client.call(pecan.request.context,
'get_rca', 'get_rca',
root=alarm_id) root=alarm_id,
all_tenants=all_tenants)
LOG.info(graph_data) LOG.info(graph_data)
graph = json.loads(graph_data) graph = json.loads(graph_data)
return graph return graph

View File

@ -34,9 +34,13 @@ LOG = log.getLogger(__name__)
class TopologyController(RootRestController): class TopologyController(RootRestController):
@pecan.expose('json') @pecan.expose('json')
def post(self, depth, graph_type, query, root): def post(self, depth, graph_type, query, root, all_tenants=0):
enforce("get topology", pecan.request.headers, if all_tenants:
pecan.request.enforcer, {}) enforce('get topology:all_tenants', pecan.request.headers,
pecan.request.enforcer, {})
else:
enforce("get topology", pecan.request.headers,
pecan.request.enforcer, {})
LOG.info(_LI('received get topology: depth->%(depth)s ' LOG.info(_LI('received get topology: depth->%(depth)s '
'graph_type->%(graph_type)s root->%(root)s') % 'graph_type->%(graph_type)s root->%(root)s') %
@ -50,18 +54,24 @@ class TopologyController(RootRestController):
if pecan.request.cfg.api.use_mock_file: if pecan.request.cfg.api.use_mock_file:
return self.get_mock_data('graph.sample.json', graph_type) return self.get_mock_data('graph.sample.json', graph_type)
else: else:
return self.get_graph(graph_type, depth, query, root) return self.get_graph(graph_type, depth, query, root, all_tenants)
@staticmethod @staticmethod
def get_graph(graph_type, depth, query, root): def get_graph(graph_type, depth, query, root, all_tenants):
TopologyController._check_input_para(graph_type, depth, query, root) TopologyController._check_input_para(graph_type,
depth,
query,
root,
all_tenants)
try: try:
graph_data = pecan.request.client.call(pecan.request.context, graph_data = pecan.request.client.call(pecan.request.context,
'get_topology', 'get_topology',
graph_type=graph_type, graph_type=graph_type,
depth=depth, depth=depth,
query=query, root=root) query=query,
root=root,
all_tenants=all_tenants)
LOG.info(graph_data) LOG.info(graph_data)
graph = json.loads(graph_data) graph = json.loads(graph_data)
if graph_type == 'graph': if graph_type == 'graph':
@ -80,7 +90,7 @@ class TopologyController(RootRestController):
abort(404, str(e)) abort(404, str(e))
@staticmethod @staticmethod
def _check_input_para(graph_type, depth, query, root): def _check_input_para(graph_type, depth, query, root, all_tenants):
if graph_type == 'graph' and depth is not None and root is None: if graph_type == 'graph' and depth is not None and root is None:
LOG.exception("Graph-type 'graph' requires a 'root' with 'depth'") LOG.exception("Graph-type 'graph' requires a 'root' with 'depth'")
abort(403, "Graph-type 'graph' requires a 'root' with 'depth'") abort(403, "Graph-type 'graph' requires a 'root' with 'depth'")

View File

@ -1,283 +0,0 @@
# Copyright 2016 - Nokia
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# 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 json
from oslo_log import log
from vitrage.common.constants import EdgeLabel
from vitrage.common.constants import EdgeProperties as EProps
from vitrage.common.constants import EntityCategory
from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE
from vitrage.datasources.nova.zone import NOVA_ZONE_DATASOURCE
from vitrage.datasources import OPENSTACK_CLUSTER
from vitrage.evaluator.template_fields import TemplateFields
from vitrage.evaluator.template_validation.status_messages import status_msgs
from vitrage.evaluator.template_validation.template_content_validator import \
content_validation
from vitrage.evaluator.template_validation.template_syntax_validator import \
syntax_validation
from vitrage.graph import create_algorithm
from vitrage.graph import Direction
LOG = log.getLogger(__name__)
# Used for Sunburst to show only specific resources
TREE_TOPOLOGY_QUERY = {
'and': [
{'==': {VProps.CATEGORY: EntityCategory.RESOURCE}},
{'==': {VProps.IS_DELETED: False}},
{'==': {VProps.IS_PLACEHOLDER: False}},
{
'or': [
{'==': {VProps.TYPE: OPENSTACK_CLUSTER}},
{'==': {VProps.TYPE: NOVA_INSTANCE_DATASOURCE}},
{'==': {VProps.TYPE: NOVA_HOST_DATASOURCE}},
{'==': {VProps.TYPE: NOVA_ZONE_DATASOURCE}}
]
}
]
}
TOPOLOGY_AND_ALARMS_QUERY = {
'and': [
{'==': {VProps.IS_DELETED: False}},
{'==': {VProps.IS_PLACEHOLDER: False}},
{
'or': [
{'==': {VProps.CATEGORY: EntityCategory.ALARM}},
{'==': {VProps.CATEGORY: EntityCategory.RESOURCE}}
]
}
]
}
RCA_QUERY = {
'and': [
{'==': {VProps.CATEGORY: EntityCategory.ALARM}},
{'==': {VProps.IS_DELETED: False}}
]
}
ALARMS_ALL_QUERY = {
'and': [
{'==': {VProps.CATEGORY: EntityCategory.ALARM}},
{'==': {VProps.IS_DELETED: False}}
]
}
class EntityGraphApis(object):
def __init__(self, entity_graph):
self.entity_graph = entity_graph
def get_alarms(self, ctx, arg):
LOG.debug("EntityGraphApis get_alarms arg:%s", str(arg))
vitrage_id = arg
if not vitrage_id or vitrage_id == 'all':
items_list = self.entity_graph.get_vertices(
query_dict=ALARMS_ALL_QUERY)
else:
items_list = self.entity_graph.neighbors(
vitrage_id,
vertex_attr_filter={VProps.CATEGORY: EntityCategory.ALARM,
VProps.IS_DELETED: False})
# TODO(alexey) this should not be here, but in the transformer
self._add_resource_details_to_alarms(items_list)
return json.dumps({'alarms': [v.properties for v in items_list]})
def get_topology(self, ctx, graph_type, depth, query, root):
LOG.debug("EntityGraphApis get_topology root:%s", str(root))
ga = create_algorithm(self.entity_graph)
if graph_type == 'tree':
if not query:
LOG.error("Graph-type 'tree' requires a filter.")
return {}
graph = ga.graph_query_vertices(
query_dict=query,
root_id=root,
depth=depth)
# By default the graph_type is 'graph'
else:
q = query if query else TOPOLOGY_AND_ALARMS_QUERY
if root:
graph = ga.graph_query_vertices(
query_dict=q,
root_id=root,
depth=depth)
else:
graph = ga.create_graph_from_matching_vertices(query_dict=q)
alarms = graph.get_vertices(query_dict=ALARMS_ALL_QUERY)
self._add_resource_details_to_alarms(alarms)
graph.update_vertices(alarms)
return graph.json_output_graph()
def get_rca(self, ctx, root):
LOG.debug("EntityGraphApis get_rca root:%s", str(root))
ga = create_algorithm(self.entity_graph)
found_graph_in = ga.graph_query_vertices(
query_dict=RCA_QUERY,
root_id=root,
direction=Direction.IN)
found_graph_out = ga.graph_query_vertices(
query_dict=RCA_QUERY,
root_id=root,
direction=Direction.OUT)
unified_graph = found_graph_in
unified_graph.union(found_graph_out)
alarms = unified_graph.get_vertices(query_dict=ALARMS_ALL_QUERY)
self._add_resource_details_to_alarms(alarms)
unified_graph.update_vertices(alarms)
json_graph = unified_graph.json_output_graph(
inspected_index=self._find_rca_index(unified_graph, root))
return json_graph
@staticmethod
def _get_first(lst):
if len(lst) == 1:
return lst[0]
else:
return None
def _add_resource_details_to_alarms(self, alarms):
for alarm in alarms:
try:
resources = self.entity_graph.neighbors(
v_id=alarm.vertex_id,
edge_attr_filter={EProps.RELATIONSHIP_TYPE: EdgeLabel.ON},
direction=Direction.OUT)
resource = self._get_first(resources)
if resource:
alarm["resource_id"] = resource.get(VProps.ID, '')
alarm["resource_type"] = resource.get(VProps.TYPE, '')
else:
alarm["resource_id"] = ''
alarm["resource_type"] = ''
except ValueError as ve:
LOG.error('Alarm %s\nException %s', alarm, ve)
@staticmethod
def _find_rca_index(found_graph, root):
for root_index, vertex in enumerate(found_graph._g):
if vertex == root:
return root_index
return 0
class TemplateApis(object):
FAILED_MSG = 'validation failed'
OK_MSG = 'validation OK'
def __init__(self, templates):
self.templates = templates
def get_templates(self, ctx):
LOG.debug("TemplateApis get_templates")
templates_details = []
for uuid, template in self.templates.items():
template_metadata = template.data[TemplateFields.METADATA]
templates_details.append({
'uuid': str(template.uuid),
'name': template_metadata[TemplateFields.NAME],
'status': self._get_template_status(template.result),
'status details': template.result.comment,
'date': template.date.strftime('%Y-%m-%dT%H:%M:%SZ')
})
return json.dumps({'templates_details': templates_details})
def show_template(self, ctx, template_uuid):
LOG.debug("Show template with uuid: $s", str(template_uuid))
template = self.templates[template_uuid]
if template:
return json.dumps(template.data)
else:
return json.dumps({'ERROR': 'Incorrect uuid'})
def validate_template(self, ctx, templates):
LOG.debug("TemplateApis validate_template templates:"
"%s", str(templates))
results = []
for template in templates:
template_def = template[1]
path = template[0]
syntax_result = syntax_validation(template_def)
if not syntax_result.is_valid:
self._add_result(path,
self.FAILED_MSG,
syntax_result.description,
syntax_result.comment,
syntax_result.status_code,
results)
continue
content_result = content_validation(template_def)
if not content_result.is_valid:
self._add_result(path,
self.FAILED_MSG,
content_result.description,
content_result.comment,
content_result.status_code,
results)
continue
self._add_result(path,
self.OK_MSG,
'Template validation',
status_msgs[0],
0,
results)
return json.dumps({'results': results})
@staticmethod
def _add_result(template_path, status, description, message, status_code,
results):
results.append({
'file path': template_path,
'status': status,
'description': description,
'message': str(message),
'status code': status_code
})
@staticmethod
def _get_template_status(result):
if result.is_valid:
return 'pass'
else:
return 'failed'

View File

@ -0,0 +1,15 @@
# Copyright 2016 - Nokia Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
__author__ = 'stack'

View File

@ -0,0 +1,102 @@
# Copyright 2016 - Nokia
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# 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 json
from oslo_log import log
from vitrage.api_handler.apis.base import ALARM_QUERY
from vitrage.api_handler.apis.base import ALARMS_ALL_QUERY
from vitrage.api_handler.apis.base import EntityGraphApisBase
from vitrage.common.constants import EntityCategory
from vitrage.common.constants import VertexProperties as VProps
LOG = log.getLogger(__name__)
class AlarmApis(EntityGraphApisBase):
def __init__(self, entity_graph, conf):
self.entity_graph = entity_graph
self.conf = conf
def get_alarms(self, ctx, vitrage_id, all_tenants):
LOG.debug("AlarmApis get_alarms - vitrage_id: %s, all_tenants=%s",
str(vitrage_id), all_tenants)
project_id = ctx.get(self.TENANT_PROPERTY, None)
is_admin_project = ctx.get(self.IS_ADMIN_PROJECT_PROPERTY, False)
if not vitrage_id or vitrage_id == 'all':
if all_tenants == "1":
alarms = self.entity_graph.get_vertices(
query_dict=ALARMS_ALL_QUERY)
else:
alarms = self._get_alarms(project_id, is_admin_project)
alarms += self._get_alarms_via_resource(project_id,
is_admin_project)
alarms = set(alarms)
else:
alarms = self.entity_graph.neighbors(
vitrage_id,
vertex_attr_filter={VProps.CATEGORY: EntityCategory.ALARM,
VProps.IS_DELETED: False})
self._add_resource_details_to_alarms(alarms)
return json.dumps({'alarms': [v.properties for v in alarms]})
def _get_alarms(self, project_id, is_admin_project):
"""Finds all the alarms with project_id
Finds all the alarms which has the project_id. In case the tenant is
admin then project_id can also be None.
:type project_id: string
:type is_admin_project: boolean
:rtype: list
"""
alarm_query = self._get_query_with_project(EntityCategory.ALARM,
project_id,
is_admin_project)
alarms = self.entity_graph.get_vertices(query_dict=alarm_query)
return self._filter_alarms(alarms, project_id)
def _get_alarms_via_resource(self, project_id, is_admin_project):
"""Finds all the alarms with project_id on their resource
Finds all the resource which has project_id and return all the alarms
on those resources project_id. In case the tenant is admin then
project_id can also be None.
:type project_id: string
:type is_admin_project: boolean
:rtype: list
"""
resource_query = self._get_query_with_project(EntityCategory.RESOURCE,
project_id,
is_admin_project)
alarms = []
resources = self.entity_graph.get_vertices(query_dict=resource_query)
for resource in resources:
new_alarms = \
self.entity_graph.neighbors(
resource.vertex_id, vertex_attr_filter=ALARM_QUERY)
alarms = alarms + new_alarms
return alarms

View File

@ -0,0 +1,214 @@
# Copyright 2016 - Nokia
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# 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 oslo_log import log
from vitrage.common.constants import EdgeLabel
from vitrage.common.constants import EdgeProperties as EProps
from vitrage.common.constants import EntityCategory
from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE
from vitrage.datasources.nova.zone import NOVA_ZONE_DATASOURCE
from vitrage.datasources import OPENSTACK_CLUSTER
from vitrage.graph import Direction
from vitrage.keystone_client import get_client as ks_client
LOG = log.getLogger(__name__)
# Used for Sunburst to show only specific resources
TREE_TOPOLOGY_QUERY = {
'and': [
{'==': {VProps.CATEGORY: EntityCategory.RESOURCE}},
{'==': {VProps.IS_DELETED: False}},
{'==': {VProps.IS_PLACEHOLDER: False}},
{
'or': [
{'==': {VProps.TYPE: OPENSTACK_CLUSTER}},
{'==': {VProps.TYPE: NOVA_INSTANCE_DATASOURCE}},
{'==': {VProps.TYPE: NOVA_HOST_DATASOURCE}},
{'==': {VProps.TYPE: NOVA_ZONE_DATASOURCE}}
]
}
]
}
TOPOLOGY_AND_ALARMS_QUERY = {
'and': [
{'==': {VProps.IS_DELETED: False}},
{'==': {VProps.IS_PLACEHOLDER: False}},
{
'or': [
{'==': {VProps.CATEGORY: EntityCategory.ALARM}},
{'==': {VProps.CATEGORY: EntityCategory.RESOURCE}}
]
}
]
}
RCA_QUERY = {
'and': [
{'==': {VProps.CATEGORY: EntityCategory.ALARM}},
{'==': {VProps.IS_DELETED: False}}
]
}
ALARMS_ALL_QUERY = {
'and': [
{'==': {VProps.CATEGORY: EntityCategory.ALARM}},
{'==': {VProps.IS_DELETED: False}}
]
}
ALARM_QUERY = {
VProps.CATEGORY: EntityCategory.ALARM,
VProps.IS_DELETED: False,
VProps.IS_PLACEHOLDER: False
}
class EntityGraphApisBase(object):
TENANT_PROPERTY = 'tenant'
IS_ADMIN_PROJECT_PROPERTY = 'is_admin'
@staticmethod
def _get_query_with_project(category, project_id, is_admin):
"""Generate query with tenant data
Creates query for entity graph which takes into consideration the
category, project_id and if the tenant is admin
:type category: string
:type project_id: string
:type is_admin: boolean
:rtype: dictionary
"""
query = {
'and': [
{'==': {VProps.IS_DELETED: False}},
{'==': {VProps.IS_PLACEHOLDER: False}},
{'==': {VProps.CATEGORY: category}}
]
}
if is_admin:
project_query = \
{'or': [{'==': {VProps.PROJECT_ID: project_id}},
{'==': {VProps.PROJECT_ID: None}}]}
else:
project_query = \
{'==': {VProps.PROJECT_ID: project_id}}
query['and'].append(project_query)
return query
def _filter_alarms(self, alarms, project_id):
"""Remove wrong alarms from the list
Removes alarms where the project_id of the resource they sit on is
different than the project_id sent as a parameter
:type alarms: list
:type project_id: string
:rtype: list
"""
alarms_to_remove = []
for alarm in alarms:
alarm_project_id = alarm.get(VProps.PROJECT_ID, None)
if not alarm_project_id:
cat_filter = {VProps.CATEGORY: EntityCategory.RESOURCE}
alarms_resource = \
self.entity_graph.neighbors(alarm.vertex_id,
vertex_attr_filter=cat_filter)
if len(alarms_resource) > 0:
resource_project_id = \
alarms_resource[0].get(VProps.PROJECT_ID, None)
if resource_project_id and \
resource_project_id != project_id:
alarms_to_remove.append(alarm)
elif alarm_project_id != project_id:
alarms_to_remove.append(alarm)
return [x for x in alarms if x not in alarms_to_remove]
def _is_alarm_of_current_project(self,
entity,
project_id,
is_admin_project):
"""Checks if the alarm is of the current tenant
Checks:
1. checks if the project_id is the same
2. if the tenant is admin then the projectid can be also None
3. check the project_id of the resource where the alarm sits is the
same as the project_id sent as a parameter
:type entity: vertex
:type project_id: string
:type is_admin_project: boolean
:rtype: boolean
"""
current_project_id = entity.get(VProps.PROJECT_ID, None)
if current_project_id == project_id:
return True
elif not current_project_id and is_admin_project:
return True
else:
entities = self.entity_graph.neighbors(entity.vertex_id,
direction=Direction.OUT)
for entity in entities:
if entity[VProps.CATEGORY] == EntityCategory.RESOURCE:
resource_project_id = entity.get(VProps.PROJECT_ID)
if resource_project_id == project_id or \
(not resource_project_id and is_admin_project):
return True
return False
return False
@staticmethod
def _get_first(lst):
if len(lst) == 1:
return lst[0]
else:
return None
def _add_resource_details_to_alarms(self, alarms):
for alarm in alarms:
try:
resources = self.entity_graph.neighbors(
v_id=alarm.vertex_id,
edge_attr_filter={EProps.RELATIONSHIP_TYPE: EdgeLabel.ON},
direction=Direction.OUT)
resource = self._get_first(resources)
if resource:
alarm["resource_id"] = resource.get(VProps.ID, '')
alarm["resource_type"] = resource.get(VProps.TYPE, '')
else:
alarm["resource_id"] = ''
alarm["resource_type"] = ''
except ValueError as ve:
LOG.error('Alarm %s\nException %s', alarm, ve)
def _is_project_admin(self, project_id):
keystone_client = ks_client(self.conf)
project = keystone_client.projects.get(project_id)
return 'name=admin' in project.to_dict()

View File

@ -0,0 +1,149 @@
# Copyright 2016 - Nokia
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# 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 oslo_log import log
from vitrage.api_handler.apis.base import ALARMS_ALL_QUERY
from vitrage.api_handler.apis.base import EntityGraphApisBase
from vitrage.api_handler.apis.base import RCA_QUERY
from vitrage.graph import create_algorithm
from vitrage.graph import Direction
LOG = log.getLogger(__name__)
class RcaApis(EntityGraphApisBase):
def __init__(self, entity_graph, conf):
self.entity_graph = entity_graph
self.conf = conf
def get_rca(self, ctx, root, all_tenants):
LOG.debug("RcaApis get_rca - root: %s, all_tenants=%s",
str(root), all_tenants)
project_id = ctx.get(self.TENANT_PROPERTY, None)
is_admin_project = ctx.get(self.IS_ADMIN_PROJECT_PROPERTY, False)
ga = create_algorithm(self.entity_graph)
found_graph_out = ga.graph_query_vertices(
query_dict=RCA_QUERY,
root_id=root,
direction=Direction.OUT)
found_graph_in = ga.graph_query_vertices(
query_dict=RCA_QUERY,
root_id=root,
direction=Direction.IN)
if all_tenants == '1':
unified_graph = found_graph_in
unified_graph.union(found_graph_out)
else:
unified_graph = \
self._get_rca_for_specific_project(ga,
found_graph_in,
found_graph_out,
root,
project_id,
is_admin_project)
alarms = unified_graph.get_vertices(query_dict=ALARMS_ALL_QUERY)
self._add_resource_details_to_alarms(alarms)
unified_graph.update_vertices(alarms)
json_graph = unified_graph.json_output_graph(
inspected_index=self._find_rca_index(unified_graph, root))
return json_graph
def _get_rca_for_specific_project(self,
ga,
found_graph_in,
found_graph_out,
root,
project_id,
is_admin_project):
"""Filter the RCA for root entity with consideration of project_id
Filter the RCA for root by:
1. filter the alarms deduced from the root alarm (found_graph_in)
2. filter the alarms caused the root alarm (found_graph_out)
And in the end unify 1 and 2
:type ga: NXAlgorithm
:type found_graph_in: NXGraph
:type found_graph_out: NXGraph
:type root: string
:type project_id: string
:type is_admin_project: boolean
:rtype: NXGraph
"""
filtered_alarms_out = \
self._filter_alarms(found_graph_out.get_vertices(), project_id)
filtered_found_graph_out = ga.subgraph(
[node.vertex_id for node in filtered_alarms_out])
filtered_found_graph_in = \
self._filter_rca_causing_entities(ga,
found_graph_in,
root,
project_id,
is_admin_project)
filtered_found_graph_out.union(filtered_found_graph_in)
return filtered_found_graph_out
def _filter_rca_causing_entities(self,
ga,
rca_graph,
root_id,
project_id,
is_admin_project):
"""Filter the RCA entities which caused this alarm
Shows only the causing alarms which has the same project_id and also
the first alarm that has a different project_id. In case the tenant is
admin then project_id can also be None.
:type ga: NXAlgorithm
:type rca_graph: NXGraph
:type root_id: string
:type project_id: string
:type is_admin_project: boolean
:rtype: NXGraph
"""
entities = [root_id]
current_entity_id = root_id
while len(rca_graph.neighbors(current_entity_id,
direction=Direction.IN)) > 0:
current_entity = rca_graph.neighbors(current_entity_id,
direction=Direction.IN)[0]
current_entity_id = current_entity.vertex_id
entities.append(current_entity.vertex_id)
if not self._is_alarm_of_current_project(current_entity,
project_id,
is_admin_project):
break
return ga.subgraph(entities)
@staticmethod
def _find_rca_index(found_graph, root):
for root_index, vertex in enumerate(found_graph._g):
if vertex == root:
return root_index
return 0

View File

@ -0,0 +1,122 @@
# Copyright 2016 - Nokia
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# 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 json
from oslo_log import log
from vitrage.evaluator.template_fields import TemplateFields
from vitrage.evaluator.template_validation.status_messages import status_msgs
from vitrage.evaluator.template_validation.template_content_validator import \
content_validation
from vitrage.evaluator.template_validation.template_syntax_validator import \
syntax_validation
LOG = log.getLogger(__name__)
class TemplateApis(object):
FAILED_MSG = 'validation failed'
OK_MSG = 'validation OK'
def __init__(self, templates):
self.templates = templates
def get_templates(self, ctx):
LOG.debug("TemplateApis get_templates")
templates_details = []
for uuid, template in self.templates.items():
template_metadata = template.data[TemplateFields.METADATA]
templates_details.append({
'uuid': str(template.uuid),
'name': template_metadata[TemplateFields.NAME],
'status': self._get_template_status(template.result),
'status details': template.result.comment,
'date': template.date.strftime('%Y-%m-%dT%H:%M:%SZ')
})
return json.dumps({'templates_details': templates_details})
def show_template(self, ctx, template_uuid):
LOG.debug("Show template with uuid: $s", str(template_uuid))
template = self.templates[template_uuid]
if template:
return json.dumps(template.data)
else:
return json.dumps({'ERROR': 'Incorrect uuid'})
def validate_template(self, ctx, templates):
LOG.debug("TemplateApis validate_template templates:"
"%s", str(templates))
results = []
for template in templates:
template_def = template[1]
path = template[0]
syntax_result = syntax_validation(template_def)
if not syntax_result.is_valid:
self._add_result(path,
self.FAILED_MSG,
syntax_result.description,
syntax_result.comment,
syntax_result.status_code,
results)
continue
content_result = content_validation(template_def)
if not content_result.is_valid:
self._add_result(path,
self.FAILED_MSG,
content_result.description,
content_result.comment,
content_result.status_code,
results)
continue
self._add_result(path,
self.OK_MSG,
'Template validation',
status_msgs[0],
0,
results)
return json.dumps({'results': results})
@staticmethod
def _add_result(template_path, status, description, message, status_code,
results):
results.append({
'file path': template_path,
'status': status,
'description': description,
'message': str(message),
'status code': status_code
})
@staticmethod
def _get_template_status(result):
if result.is_valid:
return 'pass'
else:
return 'failed'

View File

@ -0,0 +1,199 @@
# Copyright 2016 - Nokia
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# 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 oslo_log import log
from vitrage.api_handler.apis.base import ALARMS_ALL_QUERY
from vitrage.api_handler.apis.base import EntityGraphApisBase
from vitrage.api_handler.apis.base import TOPOLOGY_AND_ALARMS_QUERY
from vitrage.common.constants import EntityCategory
from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE
from vitrage.datasources import OPENSTACK_CLUSTER
from vitrage.datasources.transformer_base import build_key
from vitrage.graph import create_algorithm
LOG = log.getLogger(__name__)
class TopologyApis(EntityGraphApisBase):
def __init__(self, entity_graph, conf):
self.entity_graph = entity_graph
self.conf = conf
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(self.TENANT_PROPERTY, None)
is_admin_project = ctx.get(self.IS_ADMIN_PROJECT_PROPERTY, False)
ga = create_algorithm(self.entity_graph)
if graph_type == 'tree':
if not query:
LOG.error("Graph-type 'tree' requires a filter.")
return {}
current_query = query
if not all_tenants:
project_query = \
{'or': [{'==': {VProps.PROJECT_ID: project_id}},
{'==': {VProps.PROJECT_ID: None}}]}
current_query = {'and': [query, project_query]}
graph = ga.graph_query_vertices(
query_dict=current_query,
root_id=root,
depth=depth)
# 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)
else:
graph = \
self._get_topology_for_specific_project(
ga,
query,
project_id,
is_admin_project,
root)
alarms = graph.get_vertices(query_dict=ALARMS_ALL_QUERY)
self._add_resource_details_to_alarms(alarms)
graph.update_vertices(alarms)
return graph.json_output_graph()
def _get_topology_for_specific_project(self,
ga,
query,
project_id,
is_admin_project,
root):
"""Finds the topology in consideration with the project_id
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
:type root: string
:rtype: NXGraph
"""
if query:
q = query
else:
alarm_query = self._get_query_with_project(EntityCategory.ALARM,
project_id,
is_admin=True)
resource_query = \
self._get_query_with_project(EntityCategory.RESOURCE,
project_id,
is_admin_project)
default_query = {'or': [resource_query, alarm_query]}
q = default_query
tmp_graph = ga.create_graph_from_matching_vertices(query_dict=q)
graph = ga.subgraph(self._topology_for_unrooted_graph(ga,
tmp_graph,
root))
self._remove_alarms_of_other_projects(graph,
project_id,
is_admin_project)
return graph
def _remove_alarms_of_other_projects(self,
graph,
current_project_id,
is_admin_project):
"""Removes wrong alarms from the graph
Removes alarms of other tenants from the graph, In case the tenant is
admin then project_id can also be None.
:type graph: NXGraph
:type current_project_id: string
:type is_admin_project: boolean
"""
for alarm in graph.get_vertices(query_dict=ALARMS_ALL_QUERY):
if not alarm.get(VProps.PROJECT_ID, None):
cat_filter = {VProps.CATEGORY: EntityCategory.RESOURCE}
resource_neighbors = \
self.entity_graph.neighbors(alarm.vertex_id,
vertex_attr_filter=cat_filter)
if len(resource_neighbors) > 0:
resource_proj_id = \
resource_neighbors[0].get(VProps.PROJECT_ID, None)
cond1 = is_admin_project and resource_proj_id and \
resource_proj_id != current_project_id
cond2 = not is_admin_project and \
(not resource_proj_id or
resource_proj_id != current_project_id)
if cond1 or cond2:
graph.remove_vertex(alarm)
def _topology_for_unrooted_graph(self, ga, subgraph, root):
"""Finds topology for unrooted subgraph
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 = []
if not root:
root = build_key([EntityCategory.RESOURCE, OPENSTACK_CLUSTER])
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 += 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)
@staticmethod
def _find_instance_in_graph(graph):
for node, node_data in graph.nodes_iter(data=True):
if node_data[VProps.CATEGORY] == EntityCategory.RESOURCE and \
node_data[VProps.TYPE] == NOVA_INSTANCE_DATASOURCE:
return node
return None

View File

@ -17,8 +17,10 @@ from oslo_log import log
import oslo_messaging import oslo_messaging
from oslo_service import service as os_service from oslo_service import service as os_service
from vitrage.api_handler.apis import EntityGraphApis from vitrage.api_handler.apis.alarm import AlarmApis
from vitrage.api_handler.apis import TemplateApis from vitrage.api_handler.apis.rca import RcaApis
from vitrage.api_handler.apis.template import TemplateApis
from vitrage.api_handler.apis.topology import TopologyApis
from vitrage import messaging from vitrage import messaging
from vitrage import rpc as vitrage_rpc from vitrage import rpc as vitrage_rpc
@ -45,7 +47,9 @@ class VitrageApiHandlerService(os_service.Service):
target = oslo_messaging.Target(topic=self.conf.rpc_topic, target = oslo_messaging.Target(topic=self.conf.rpc_topic,
server=rabbit_hosts) server=rabbit_hosts)
endpoints = [EntityGraphApis(self.entity_graph), endpoints = [TopologyApis(self.entity_graph, self.conf),
AlarmApis(self.entity_graph, self.conf),
RcaApis(self.entity_graph, self.conf),
TemplateApis(self.scenario_repo.templates)] TemplateApis(self.scenario_repo.templates)]
server = vitrage_rpc.get_server(target, endpoints, transport) server = vitrage_rpc.get_server(target, endpoints, transport)

View File

@ -58,8 +58,49 @@ class GraphAlgorithm(object):
""" """
pass pass
@abc.abstractmethod
def subgraph(self, entities):
"""Return the subgraph induced on nodes in entities.
The induced subgraph of the graph contains the nodes in entities and
the edges between those nodes.
:type entities: list
:rtype: NXGraph
"""
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, vertex_attr_filter=None,
query_dict=None): query_dict=None):
"""Generate graph using the query
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
:rtype: NXGraph
"""
pass pass

View File

@ -12,6 +12,9 @@
# 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.graph.algo_driver.algorithm import GraphAlgorithm from vitrage.graph.algo_driver.algorithm import GraphAlgorithm
@ -102,3 +105,17 @@ class NXAlgorithm(GraphAlgorithm):
str(self.graph._g.nodes(data=True)), str(self.graph._g.nodes(data=True)),
str(self.graph._g.edges(data=True))) str(self.graph._g.edges(data=True)))
return graph return graph
def subgraph(self, entities):
subgraph = NXGraph('graph')
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)

View File

@ -27,6 +27,7 @@ def create_vertex(vitrage_id,
sample_timestamp=None, sample_timestamp=None,
update_timestamp=None, update_timestamp=None,
is_placeholder=False, is_placeholder=False,
project_id=None,
metadata=None): metadata=None):
"""A builder to create a vertex """A builder to create a vertex
@ -50,6 +51,8 @@ def create_vertex(vitrage_id,
:type metadata: dict :type metadata: dict
:param is_placeholder: :param is_placeholder:
:type is_placeholder: boolean :type is_placeholder: boolean
:param project_id:
:type project_id: str
:return: :return:
:rtype: Vertex :rtype: Vertex
""" """
@ -63,7 +66,8 @@ def create_vertex(vitrage_id,
VConst.UPDATE_TIMESTAMP: update_timestamp, VConst.UPDATE_TIMESTAMP: update_timestamp,
VConst.SAMPLE_TIMESTAMP: sample_timestamp, VConst.SAMPLE_TIMESTAMP: sample_timestamp,
VConst.IS_PLACEHOLDER: is_placeholder, VConst.IS_PLACEHOLDER: is_placeholder,
VConst.VITRAGE_ID: vitrage_id VConst.VITRAGE_ID: vitrage_id,
VConst.PROJECT_ID: project_id
} }
if metadata: if metadata:
properties.update(metadata) properties.update(metadata)

View File

@ -0,0 +1,15 @@
# Copyright 2016 - Nokia
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
__author__ = 'stack'

View File

@ -0,0 +1,324 @@
# Copyright 2016 - Nokia
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# 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 json
from vitrage.api_handler.apis.alarm import AlarmApis
from vitrage.api_handler.apis.rca import RcaApis
from vitrage.api_handler.apis.topology import TopologyApis
from vitrage.common.constants import EntityCategory
from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources import NOVA_HOST_DATASOURCE
from vitrage.datasources import NOVA_INSTANCE_DATASOURCE
from vitrage.datasources import NOVA_ZONE_DATASOURCE
from vitrage.datasources import OPENSTACK_CLUSTER
from vitrage.graph import NXGraph
import vitrage.graph.utils as graph_utils
from vitrage.tests.unit.entity_graph.base import TestEntityGraphUnitBase
class TestApis(TestEntityGraphUnitBase):
def test_get_alarms_with_admin_project(self):
# Setup
graph = self._create_graph()
apis = AlarmApis(graph, None)
ctx = {'tenant': 'project_1', 'is_admin': True}
# Action
alarms = apis.get_alarms(ctx, vitrage_id='all', all_tenants='0')
alarms = json.loads(alarms)['alarms']
# Test assertions
self.assertEqual(3, len(alarms))
self._check_projects_entities(alarms, 'project_1', True)
def test_get_alarms_with_not_admin_project(self):
# Setup
graph = self._create_graph()
apis = AlarmApis(graph, None)
ctx = {'tenant': 'project_2', 'is_admin': False}
# Action
alarms = apis.get_alarms(ctx, vitrage_id='all', all_tenants='0')
alarms = json.loads(alarms)['alarms']
# Test assertions
self.assertEqual(2, len(alarms))
self._check_projects_entities(alarms, 'project_2', True)
def test_get_alarms_with_all_tenants(self):
# Setup
graph = self._create_graph()
apis = AlarmApis(graph, None)
ctx = {'tenant': 'project_1', 'is_admin': False}
# Action
alarms = apis.get_alarms(ctx, vitrage_id='all', all_tenants='1')
alarms = json.loads(alarms)['alarms']
# Test assertions
self.assertEqual(5, len(alarms))
self._check_projects_entities(alarms, None, True)
def test_get_rca_with_admin_project(self):
# Setup
graph = self._create_graph()
apis = RcaApis(graph, None)
ctx = {'tenant': 'project_1', 'is_admin': True}
# Action
graph_rca = apis.get_rca(ctx, root='alarm_on_host', all_tenants='0')
graph_rca = json.loads(graph_rca)
# Test assertions
self.assertEqual(3, len(graph_rca['nodes']))
self._check_projects_entities(graph_rca['nodes'], 'project_1', True)
def test_get_rca_with_not_admin_project(self):
# Setup
graph = self._create_graph()
apis = RcaApis(graph, None)
ctx = {'tenant': 'project_2', 'is_admin': False}
# Action
graph_rca = apis.get_rca(ctx,
root='alarm_on_instance_3',
all_tenants='0')
graph_rca = json.loads(graph_rca)
# Test assertions
self.assertEqual(2, len(graph_rca['nodes']))
self._check_projects_entities(graph_rca['nodes'], 'project_2', True)
def test_get_rca_with_not_admin_bla_project(self):
# Setup
graph = self._create_graph()
apis = RcaApis(graph, None)
ctx = {'tenant': 'project_2', 'is_admin': False}
# Action
graph_rca = apis.get_rca(ctx, root='alarm_on_host', all_tenants='0')
graph_rca = json.loads(graph_rca)
# Test assertions
self.assertEqual(3, len(graph_rca['nodes']))
self._check_projects_entities(graph_rca['nodes'], 'project_2', True)
def test_get_rca_with_all_tenants(self):
# Setup
graph = self._create_graph()
apis = RcaApis(graph, None)
ctx = {'tenant': 'project_1', 'is_admin': False}
# Action
graph_rca = apis.get_rca(ctx, root='alarm_on_host', all_tenants='1')
graph_rca = json.loads(graph_rca)
# Test assertions
self.assertEqual(5, len(graph_rca['nodes']))
self._check_projects_entities(graph_rca['nodes'], None, True)
def test_get_topology_with_admin_project(self):
# Setup
graph = self._create_graph()
apis = TopologyApis(graph, None)
ctx = {'tenant': 'project_1', 'is_admin': True}
# Action
graph_topology = apis.get_topology(ctx,
graph_type='graph',
depth=10,
query=None,
root='RESOURCE:openstack.cluster',
all_tenants=0)
graph_topology = json.loads(graph_topology)
# Test assertions
self.assertEqual(8, len(graph_topology['nodes']))
self._check_projects_entities(graph_topology['nodes'],
'project_1',
False)
def test_get_topology_with_not_admin_project(self):
# Setup
graph = self._create_graph()
apis = TopologyApis(graph, None)
ctx = {'tenant': 'project_2', 'is_admin': False}
# Action
graph_topology = apis.get_topology(ctx,
graph_type='graph',
depth=10,
query=None,
root='RESOURCE:openstack.cluster',
all_tenants=0)
graph_topology = json.loads(graph_topology)
# Test assertions
self.assertEqual(7, len(graph_topology['nodes']))
self._check_projects_entities(graph_topology['nodes'],
'project_2',
False)
def test_get_topology_with_all_tenants(self):
# Setup
graph = self._create_graph()
apis = TopologyApis(graph, None)
ctx = {'tenant': 'project_1', 'is_admin': False}
# Action
graph_topology = apis.get_topology(ctx,
graph_type='graph',
depth=10,
query=None,
root='RESOURCE:openstack.cluster',
all_tenants=1)
graph_topology = json.loads(graph_topology)
# Test assertions
self.assertEqual(12, len(graph_topology['nodes']))
def _check_projects_entities(self,
alarms,
project_id,
check_alarm_category):
for alarm in alarms:
tmp_project_id = alarm.get(VProps.PROJECT_ID, None)
condition = True
if check_alarm_category:
condition = alarm[VProps.CATEGORY] == EntityCategory.ALARM
if project_id:
condition = condition and \
(not tmp_project_id or
(tmp_project_id and tmp_project_id == project_id))
self.assertEqual(True, condition)
def _create_graph(self):
graph = NXGraph('Multi tenancy graph')
# create vertices
cluster_vertex = self._create_resource('RESOURCE:openstack.cluster',
OPENSTACK_CLUSTER)
zone_vertex = self._create_resource('zone_1',
NOVA_ZONE_DATASOURCE)
host_vertex = self._create_resource('host_1',
NOVA_HOST_DATASOURCE)
instance_1_vertex = self._create_resource('instance_1',
NOVA_INSTANCE_DATASOURCE,
project_id='project_1')
instance_2_vertex = self._create_resource('instance_2',
NOVA_INSTANCE_DATASOURCE,
project_id='project_1')
instance_3_vertex = self._create_resource('instance_3',
NOVA_INSTANCE_DATASOURCE,
project_id='project_2')
instance_4_vertex = self._create_resource('instance_4',
NOVA_INSTANCE_DATASOURCE,
project_id='project_2')
alarm_on_host_vertex = self._create_alarm('alarm_on_host',
'alarm_on_host')
alarm_on_instance_1_vertex = self._create_alarm('alarm_on_instance_1',
'deduced_alarm',
project_id='project_1')
alarm_on_instance_2_vertex = self._create_alarm('alarm_on_instance_2',
'deduced_alarm')
alarm_on_instance_3_vertex = self._create_alarm('alarm_on_instance_3',
'deduced_alarm',
project_id='project_2')
alarm_on_instance_4_vertex = self._create_alarm('alarm_on_instance_4',
'deduced_alarm')
# create links
edges = list()
edges.append(graph_utils.create_edge(
cluster_vertex.vertex_id,
zone_vertex.vertex_id,
'contains'))
edges.append(graph_utils.create_edge(
zone_vertex.vertex_id,
host_vertex.vertex_id,
'contains'))
edges.append(graph_utils.create_edge(
host_vertex.vertex_id,
instance_1_vertex.vertex_id,
'contains'))
edges.append(graph_utils.create_edge(
host_vertex.vertex_id,
instance_2_vertex.vertex_id,
'contains'))
edges.append(graph_utils.create_edge(
host_vertex.vertex_id,
instance_3_vertex.vertex_id,
'contains'))
edges.append(graph_utils.create_edge(
host_vertex.vertex_id,
instance_4_vertex.vertex_id,
'contains'))
edges.append(graph_utils.create_edge(
alarm_on_host_vertex.vertex_id,
host_vertex.vertex_id,
'on'))
edges.append(graph_utils.create_edge(
alarm_on_instance_1_vertex.vertex_id,
instance_1_vertex.vertex_id,
'on'))
edges.append(graph_utils.create_edge(
alarm_on_instance_2_vertex.vertex_id,
instance_2_vertex.vertex_id,
'on'))
edges.append(graph_utils.create_edge(
alarm_on_instance_3_vertex.vertex_id,
instance_3_vertex.vertex_id,
'on'))
edges.append(graph_utils.create_edge(
alarm_on_instance_4_vertex.vertex_id,
instance_4_vertex.vertex_id,
'on'))
edges.append(graph_utils.create_edge(
alarm_on_host_vertex.vertex_id,
alarm_on_instance_1_vertex.vertex_id,
'causes'))
edges.append(graph_utils.create_edge(
alarm_on_host_vertex.vertex_id,
alarm_on_instance_2_vertex.vertex_id,
'causes'))
edges.append(graph_utils.create_edge(
alarm_on_host_vertex.vertex_id,
alarm_on_instance_3_vertex.vertex_id,
'causes'))
edges.append(graph_utils.create_edge(
alarm_on_host_vertex.vertex_id,
alarm_on_instance_4_vertex.vertex_id,
'causes'))
# add vertices to graph
graph.add_vertex(cluster_vertex)
graph.add_vertex(zone_vertex)
graph.add_vertex(host_vertex)
graph.add_vertex(instance_1_vertex)
graph.add_vertex(instance_2_vertex)
graph.add_vertex(instance_3_vertex)
graph.add_vertex(instance_4_vertex)
graph.add_vertex(alarm_on_host_vertex)
graph.add_vertex(alarm_on_instance_1_vertex)
graph.add_vertex(alarm_on_instance_2_vertex)
graph.add_vertex(alarm_on_instance_3_vertex)
graph.add_vertex(alarm_on_instance_4_vertex)
# add links to graph
for edge in edges:
graph.add_edge(edge)
return graph

View File

@ -17,7 +17,6 @@ from oslo_config import cfg
from vitrage.common.constants import DatasourceProperties as DSProps from vitrage.common.constants import DatasourceProperties as DSProps
from vitrage.common.constants import EntityCategory from vitrage.common.constants import EntityCategory
from vitrage.common.constants import SyncMode from vitrage.common.constants import SyncMode
from vitrage.common.datetime_utils import utcnow
from vitrage.datasources.nagios import NAGIOS_DATASOURCE from vitrage.datasources.nagios import NAGIOS_DATASOURCE
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE
@ -134,7 +133,7 @@ class TestEntityGraphUnitBase(base.BaseTest):
return events_list[0] return events_list[0]
@staticmethod @staticmethod
def _create_alarm(vitrage_id, alarm_type): def _create_alarm(vitrage_id, alarm_type, project_id=None):
return graph_utils.create_vertex( return graph_utils.create_vertex(
vitrage_id, vitrage_id,
entity_id=vitrage_id, entity_id=vitrage_id,
@ -142,8 +141,23 @@ class TestEntityGraphUnitBase(base.BaseTest):
entity_type=alarm_type, entity_type=alarm_type,
entity_state='active', entity_state='active',
is_deleted=False, is_deleted=False,
sample_timestamp=utcnow(), sample_timestamp=None,
is_placeholder=False, is_placeholder=False,
project_id=project_id
)
@staticmethod
def _create_resource(vitrage_id, resource_type, project_id=None):
return graph_utils.create_vertex(
vitrage_id,
entity_id=vitrage_id,
entity_category=EntityCategory.RESOURCE,
entity_type=resource_type,
entity_state='active',
is_deleted=False,
sample_timestamp=None,
is_placeholder=False,
project_id=project_id
) )
def _num_total_expected_vertices(self): def _num_total_expected_vertices(self):

View File

@ -100,7 +100,7 @@ class BaseApiTest(base.BaseTestCase):
return volume return volume
def _get_host(self): def _get_host(self):
topology = self.vitrage_client.topology.get() topology = self.vitrage_client.topology.get(all_tenants=1)
host = filter(lambda item: item[VProps.TYPE] == NOVA_HOST_DATASOURCE, host = filter(lambda item: item[VProps.TYPE] == NOVA_HOST_DATASOURCE,
topology['nodes']) topology['nodes'])
return host[0] return host[0]

View File

@ -34,7 +34,7 @@ class TestAodhAlarm(BaseAlarmsTest):
self._create_ceilometer_alarm(self._find_instance_resource_id()) self._create_ceilometer_alarm(self._find_instance_resource_id())
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get() api_graph = self.vitrage_client.topology.get(all_tenants=1)
graph = self._create_graph_from_graph_dictionary(api_graph) graph = self._create_graph_from_graph_dictionary(api_graph)
entities = self._entities_validation_data( entities = self._entities_validation_data(
host_entities=1, host_entities=1,
@ -64,7 +64,7 @@ class TestAodhAlarm(BaseAlarmsTest):
self._create_ceilometer_alarm() self._create_ceilometer_alarm()
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get() api_graph = self.vitrage_client.topology.get(all_tenants=1)
graph = self._create_graph_from_graph_dictionary(api_graph) graph = self._create_graph_from_graph_dictionary(api_graph)
entities = self._entities_validation_data( entities = self._entities_validation_data(
host_entities=1, host_entities=1,

View File

@ -33,7 +33,7 @@ class TestCinderVolume(BaseTopologyTest):
num_volumes=self.NUM_VOLUME) num_volumes=self.NUM_VOLUME)
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get() api_graph = self.vitrage_client.topology.get(all_tenants=1)
graph = self._create_graph_from_graph_dictionary(api_graph) graph = self._create_graph_from_graph_dictionary(api_graph)
entities = self._entities_validation_data( entities = self._entities_validation_data(
host_entities=1, host_entities=1,

View File

@ -38,7 +38,7 @@ class TestHeatStack(BaseTopologyTest):
self._create_stacks(num_stacks=self.NUM_STACKS) self._create_stacks(num_stacks=self.NUM_STACKS)
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get() api_graph = self.vitrage_client.topology.get(all_tenants=1)
graph = self._create_graph_from_graph_dictionary(api_graph) graph = self._create_graph_from_graph_dictionary(api_graph)
entities = self._entities_validation_data( entities = self._entities_validation_data(
host_entities=1, host_entities=1,

View File

@ -39,7 +39,7 @@ class TestNeutron(BaseTopologyTest):
set_public_network=True) set_public_network=True)
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get() api_graph = self.vitrage_client.topology.get(all_tenants=1)
graph = self._create_graph_from_graph_dictionary(api_graph) graph = self._create_graph_from_graph_dictionary(api_graph)
entities = self._entities_validation_data( entities = self._entities_validation_data(
host_entities=1, host_entities=1,

View File

@ -31,7 +31,7 @@ class TestNova(BaseTopologyTest):
self._create_entities(num_instances=self.NUM_INSTANCE) self._create_entities(num_instances=self.NUM_INSTANCE)
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get() api_graph = self.vitrage_client.topology.get(all_tenants=1)
graph = self._create_graph_from_graph_dictionary(api_graph) graph = self._create_graph_from_graph_dictionary(api_graph)
entities = self._entities_validation_data( entities = self._entities_validation_data(
host_entities=1, host_entities=1,

View File

@ -36,7 +36,7 @@ class TestStaticPhysical(BaseApiTest):
self._create_switches() self._create_switches()
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get() api_graph = self.vitrage_client.topology.get(all_tenants=1)
graph = self._create_graph_from_graph_dictionary(api_graph) graph = self._create_graph_from_graph_dictionary(api_graph)
entities = self._entities_validation_data( entities = self._entities_validation_data(
host_entities=1, host_entities=1,

View File

@ -116,7 +116,7 @@ class TestRca(BaseRcaTest):
self._create_alarm( self._create_alarm(
resource_id=self._get_hostname(), resource_id=self._get_hostname(),
alarm_name=RCA_ALARM_NAME) alarm_name=RCA_ALARM_NAME)
topology = self.vitrage_client.topology.get() topology = self.vitrage_client.topology.get(all_tenants=1)
self._validate_set_state(topology=topology['nodes'], self._validate_set_state(topology=topology['nodes'],
instances=instances) instances=instances)

View File

@ -34,10 +34,16 @@ class BaseTopologyTest(BaseApiTest):
def _rollback_to_default(self): def _rollback_to_default(self):
self._delete_entities() self._delete_entities()
api_graph = self.vitrage_client.topology.get( api_graph = self.vitrage_client.topology.get(
limit=4, root='RESOURCE:openstack.cluster') limit=4, root='RESOURCE:openstack.cluster', all_tenants=1)
graph = self._create_graph_from_graph_dictionary(api_graph) graph = self._create_graph_from_graph_dictionary(api_graph)
entities = self._entities_validation_data() entities = self._entities_validation_data()
self._validate_graph_correctness(graph, 3, 2, entities) num_default_entities = self.num_default_entities + \
self.num_default_networks + self.num_default_ports
num_default_edges = self.num_default_edges + self.num_default_ports
self._validate_graph_correctness(graph,
num_default_entities,
num_default_edges,
entities)
def _create_entities(self, num_instances=0, num_volumes=0, end_sleep=3): def _create_entities(self, num_instances=0, num_volumes=0, end_sleep=3):
if num_instances > 0: if num_instances > 0:

View File

@ -18,6 +18,9 @@ from vitrage_tempest_tests.tests.api.topology.base import BaseTopologyTest
import vitrage_tempest_tests.tests.utils as utils import vitrage_tempest_tests.tests.utils as utils
from vitrageclient.exc import ClientException from vitrageclient.exc import ClientException
import unittest
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
NOVA_QUERY = '{"and": [{"==": {"category": "RESOURCE"}},' \ NOVA_QUERY = '{"and": [{"==": {"category": "RESOURCE"}},' \
'{"==": {"is_deleted": false}},' \ '{"==": {"is_deleted": false}},' \
@ -60,7 +63,7 @@ class TestTopology(BaseTopologyTest):
num_volumes=self.NUM_VOLUME) num_volumes=self.NUM_VOLUME)
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get() api_graph = self.vitrage_client.topology.get(all_tenants=1)
graph = self._create_graph_from_graph_dictionary(api_graph) graph = self._create_graph_from_graph_dictionary(api_graph)
entities = self._entities_validation_data( entities = self._entities_validation_data(
host_entities=1, host_entities=1,
@ -96,7 +99,8 @@ class TestTopology(BaseTopologyTest):
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get( api_graph = self.vitrage_client.topology.get(
query=self._graph_query()) query=self._graph_query(),
all_tenants=1)
graph = self._create_graph_from_graph_dictionary(api_graph) graph = self._create_graph_from_graph_dictionary(api_graph)
entities = self._entities_validation_data( entities = self._entities_validation_data(
host_entities=1, host_entities=1,
@ -126,7 +130,7 @@ class TestTopology(BaseTopologyTest):
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get( api_graph = self.vitrage_client.topology.get(
graph_type='tree', query=NOVA_QUERY) graph_type='tree', query=NOVA_QUERY, all_tenants=1)
graph = self._create_graph_from_tree_dictionary(api_graph) graph = self._create_graph_from_tree_dictionary(api_graph)
entities = self._entities_validation_data( entities = self._entities_validation_data(
host_entities=1, host_entities=1,
@ -156,7 +160,7 @@ class TestTopology(BaseTopologyTest):
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get( api_graph = self.vitrage_client.topology.get(
graph_type='tree', query=self._tree_query()) graph_type='tree', query=self._tree_query(), all_tenants=1)
graph = self._create_graph_from_tree_dictionary(api_graph) graph = self._create_graph_from_tree_dictionary(api_graph)
entities = self._entities_validation_data( entities = self._entities_validation_data(
host_entities=1, host_edges=1) host_entities=1, host_edges=1)
@ -181,7 +185,7 @@ class TestTopology(BaseTopologyTest):
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get( api_graph = self.vitrage_client.topology.get(
limit=2, graph_type='tree', query=NOVA_QUERY) limit=2, graph_type='tree', query=NOVA_QUERY, all_tenants=1)
graph = self._create_graph_from_tree_dictionary(api_graph) graph = self._create_graph_from_tree_dictionary(api_graph)
entities = self._entities_validation_data( entities = self._entities_validation_data(
host_entities=1, host_edges=1) host_entities=1, host_edges=1)
@ -206,7 +210,7 @@ class TestTopology(BaseTopologyTest):
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get( api_graph = self.vitrage_client.topology.get(
limit=3, graph_type='tree', query=NOVA_QUERY) limit=3, graph_type='tree', query=NOVA_QUERY, all_tenants=1)
graph = self._create_graph_from_tree_dictionary(api_graph) graph = self._create_graph_from_tree_dictionary(api_graph)
entities = self._entities_validation_data( entities = self._entities_validation_data(
host_entities=1, host_entities=1,
@ -224,6 +228,7 @@ class TestTopology(BaseTopologyTest):
finally: finally:
self._rollback_to_default() self._rollback_to_default()
@unittest.skip("testing skipping")
def test_graph_with_root_and_depth_exclude_instance(self): def test_graph_with_root_and_depth_exclude_instance(self):
"""tree_with_query """tree_with_query
@ -236,7 +241,7 @@ class TestTopology(BaseTopologyTest):
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get( api_graph = self.vitrage_client.topology.get(
limit=2, root='RESOURCE:openstack.cluster') limit=2, root='RESOURCE:openstack.cluster', all_tenants=1)
graph = self._create_graph_from_graph_dictionary(api_graph) graph = self._create_graph_from_graph_dictionary(api_graph)
entities = self._entities_validation_data( entities = self._entities_validation_data(
host_entities=1, host_edges=1) host_entities=1, host_edges=1)
@ -249,6 +254,7 @@ class TestTopology(BaseTopologyTest):
finally: finally:
self._rollback_to_default() self._rollback_to_default()
@unittest.skip("testing skipping")
def test_graph_with_root_and_depth_include_instance(self): def test_graph_with_root_and_depth_include_instance(self):
"""graph_with_root_and_depth_include_instance """graph_with_root_and_depth_include_instance
@ -261,7 +267,7 @@ class TestTopology(BaseTopologyTest):
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get( api_graph = self.vitrage_client.topology.get(
limit=3, root='RESOURCE:openstack.cluster') limit=3, root='RESOURCE:openstack.cluster', all_tenants=1)
graph = self._create_graph_from_graph_dictionary(api_graph) graph = self._create_graph_from_graph_dictionary(api_graph)
entities = self._entities_validation_data( entities = self._entities_validation_data(
host_entities=1, host_entities=1,
@ -292,7 +298,8 @@ class TestTopology(BaseTopologyTest):
# Calculate expected results # Calculate expected results
self.vitrage_client.topology.get(limit=2, self.vitrage_client.topology.get(limit=2,
root='RESOURCE:openstack.cluster') root='RESOURCE:openstack.cluster',
all_tenants=1)
except ClientException as e: except ClientException as e:
self.assertEqual(403, e.code) self.assertEqual(403, e.code)
self.assertEqual( self.assertEqual(
@ -314,7 +321,7 @@ class TestTopology(BaseTopologyTest):
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get( api_graph = self.vitrage_client.topology.get(
query=self._graph_no_match_query()) query=self._graph_no_match_query(), all_tenants=1)
# Test Assertions # Test Assertions
self.assertEqual( self.assertEqual(
@ -338,7 +345,9 @@ class TestTopology(BaseTopologyTest):
# Calculate expected results # Calculate expected results
api_graph = self.vitrage_client.topology.get( api_graph = self.vitrage_client.topology.get(
graph_type='tree', query=self._tree_no_match_query()) graph_type='tree',
query=self._tree_no_match_query(),
all_tenants=1)
# Test Assertions # Test Assertions
self.assertEqual({}, api_graph) self.assertEqual({}, api_graph)