From 17b0322679ecf9d4b5a1035b020c36f6eee6f49b Mon Sep 17 00:00:00 2001 From: Tyler Smith Date: Wed, 2 Aug 2017 16:08:42 -0400 Subject: [PATCH] Implements alarm counts API This change is to introduce a new rest api (v1/alarm/count) to vitrage to provide counts of any alarms present in the system. - It can optionally provide counts of alarms for all_tenants similarly to the alarm api - The rest api doc has been updated - Unit test were added to test the api with and without all_tenants as well as noauth Change-Id: I6061b63c068580dcd25df5c624d9b3bd88f30cca Implements: blueprint alarm-counts-api Signed-off-by: Tyler Smith --- doc/source/contributor/vitrage-api.rst | 62 +++++++++++++++++++ etc/vitrage/policy.json | 2 + vitrage/api/controllers/v1/alarm.py | 2 + vitrage/api/controllers/v1/count.py | 54 ++++++++++++++++ vitrage/api_handler/apis/alarm.py | 30 +++++++++ .../tests/functional/api/v1/test_noauth.py | 12 +++- .../tests/functional/api_handler/test_apis.py | 56 +++++++++++++++-- 7 files changed, 211 insertions(+), 7 deletions(-) mode change 100644 => 100755 doc/source/contributor/vitrage-api.rst mode change 100644 => 100755 etc/vitrage/policy.json mode change 100644 => 100755 vitrage/api/controllers/v1/alarm.py create mode 100755 vitrage/api/controllers/v1/count.py mode change 100644 => 100755 vitrage/api_handler/apis/alarm.py mode change 100644 => 100755 vitrage/tests/functional/api/v1/test_noauth.py mode change 100644 => 100755 vitrage/tests/functional/api_handler/test_apis.py diff --git a/doc/source/contributor/vitrage-api.rst b/doc/source/contributor/vitrage-api.rst old mode 100644 new mode 100755 index 1460c210a..b219239e0 --- a/doc/source/contributor/vitrage-api.rst +++ b/doc/source/contributor/vitrage-api.rst @@ -858,6 +858,68 @@ Response Examples } ] +Show Alarm Count +^^^^^^^^^^^^^^^^ + +Shows how many alarms of each operations severity exist + +GET /v1/alarm/count +~~~~~~~~~~~~~~~~~~~ + +Headers +======= + +- X-Auth-Token (string, required) - Keystone auth token + +Path Parameters +=============== + +None. + +Query Parameters +================ + +None. + +Request Body +============ + +* all_tenants - (boolean, optional) includes alarms of all tenants in the count (in case the user has the permissions). + +Request Examples +================ + +:: + + GET /v1/alarm/count/ HTTP/1.1 + Host: 135.248.19.18:8999 + X-Auth-Token: 2b8882ba2ec44295bf300aecb2caa4f7 + Accept: application/json + +Response Status code +==================== + +- 200 - OK + +Response Body +============= + +Returns a JSON object with all the alarms requested. + +Response Examples +================= + +:: + + { + "severe": 2, + "critical": 1, + "warning": 3, + "na": 4, + "ok": 5 + } + + Template Validate ^^^^^^^^^^^^^^^^^ diff --git a/etc/vitrage/policy.json b/etc/vitrage/policy.json old mode 100644 new mode 100755 index 2b7ad9665..aca84ee1e --- a/etc/vitrage/policy.json +++ b/etc/vitrage/policy.json @@ -6,6 +6,8 @@ "list resources:all_tenants": "role:admin", "list alarms": "", "list alarms:all_tenants": "role:admin", + "get alarms count": "", + "get alarms count:all_tenants": "role:admin", "get rca": "", "get rca:all_tenants": "role:admin", "template validate": "", diff --git a/vitrage/api/controllers/v1/alarm.py b/vitrage/api/controllers/v1/alarm.py old mode 100644 new mode 100755 index 187c923ed..e0a56f7c7 --- a/vitrage/api/controllers/v1/alarm.py +++ b/vitrage/api/controllers/v1/alarm.py @@ -21,6 +21,7 @@ from osprofiler import profiler from pecan.core import abort from vitrage.api.controllers.rest import RootRestController +from vitrage.api.controllers.v1 import count from vitrage.api.policy import enforce @@ -30,6 +31,7 @@ LOG = log.getLogger(__name__) @profiler.trace_cls("alarm controller", info={}, hide_args=False, trace_private=False) class AlarmsController(RootRestController): + count = count.CountsController() @pecan.expose('json') def index(self, vitrage_id, all_tenants=False): diff --git a/vitrage/api/controllers/v1/count.py b/vitrage/api/controllers/v1/count.py new file mode 100755 index 000000000..d716f858f --- /dev/null +++ b/vitrage/api/controllers/v1/count.py @@ -0,0 +1,54 @@ +# 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 +import pecan + +from oslo_log import log +from oslo_utils.strutils import bool_from_string +from pecan.core import abort + +from vitrage.api.controllers.rest import RootRestController +from vitrage.api.policy import enforce + + +LOG = log.getLogger(__name__) + + +class CountsController(RootRestController): + + @pecan.expose('json') + def index(self, all_tenants=False): + return self.get(all_tenants) + + @pecan.expose('json') + def get(self, all_tenants=False): + all_tenants = bool_from_string(all_tenants) + if all_tenants: + enforce("get alarms count:all_tenants", pecan.request.headers, + pecan.request.enforcer, {}) + else: + enforce("get alarms count", pecan.request.headers, + pecan.request.enforcer, {}) + + LOG.info('received get alarm counts') + + try: + alarm_counts_json = pecan.request.client.call( + pecan.request.context, 'get_alarm_counts', + all_tenants=all_tenants) + + return json.loads(alarm_counts_json) + + except Exception as e: + LOG.exception('failed to get alarm counts %s', e) + abort(404, str(e)) diff --git a/vitrage/api_handler/apis/alarm.py b/vitrage/api_handler/apis/alarm.py old mode 100644 new mode 100755 index 9a0544ed4..c78c8c373 --- a/vitrage/api_handler/apis/alarm.py +++ b/vitrage/api_handler/apis/alarm.py @@ -21,6 +21,8 @@ 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 +from vitrage.entity_graph.mappings.operational_alarm_severity import \ + OperationalAlarmSeverity LOG = log.getLogger(__name__) @@ -58,6 +60,34 @@ class AlarmApis(EntityGraphApisBase): return json.dumps({'alarms': [v.properties for v in alarms]}) + def get_alarm_counts(self, ctx, all_tenants): + LOG.debug("AlarmApis get_alarm_counts - all_tenants=%s", all_tenants) + + project_id = ctx.get(self.TENANT_PROPERTY, None) + is_admin_project = ctx.get(self.IS_ADMIN_PROJECT_PROPERTY, False) + + if all_tenants: + 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) + + counts = {OperationalAlarmSeverity.SEVERE: 0, + OperationalAlarmSeverity.CRITICAL: 0, + OperationalAlarmSeverity.WARNING: 0, + OperationalAlarmSeverity.OK: 0, + OperationalAlarmSeverity.NA: 0} + + for alarm in alarms: + severity = alarm.get(VProps.VITRAGE_OPERATIONAL_SEVERITY) + if severity: + counts[severity] += 1 + + return json.dumps(counts) + def _get_alarms(self, project_id, is_admin_project): """Finds all the alarms with project_id diff --git a/vitrage/tests/functional/api/v1/test_noauth.py b/vitrage/tests/functional/api/v1/test_noauth.py old mode 100644 new mode 100755 index 0da7cad17..84691e2c0 --- a/vitrage/tests/functional/api/v1/test_noauth.py +++ b/vitrage/tests/functional/api/v1/test_noauth.py @@ -52,7 +52,6 @@ class NoAuthTest(FunctionalTest): self.assertEqual('200 OK', resp.status) def test_noauth_mode_get_topology(self): - with mock.patch('pecan.request') as request: request.client.call.return_value = '{}' params = dict(depth=None, graph_type='graph', query=None, @@ -65,7 +64,6 @@ class NoAuthTest(FunctionalTest): self.assertEqual({}, resp.json) def test_noauth_mode_list_alarms(self): - with mock.patch('pecan.request') as request: request.client.call.return_value = '{"alarms": []}' params = dict(vitrage_id='all', all_tenants=False) @@ -75,6 +73,16 @@ class NoAuthTest(FunctionalTest): self.assertEqual('200 OK', resp.status) self.assertEqual([], resp.json) + def test_noauth_mode_show_alarm_count(self): + with mock.patch('pecan.request') as request: + request.client.call.return_value = '{}' + params = dict(all_tenants=False) + resp = self.post_json('/alarm/count/', params=params) + + self.assertEqual(1, request.client.call.call_count) + self.assertEqual('200 OK', resp.status) + self.assertEqual({}, resp.json) + def test_noauth_mode_list_resources(self): with mock.patch('pecan.request') as request: diff --git a/vitrage/tests/functional/api_handler/test_apis.py b/vitrage/tests/functional/api_handler/test_apis.py old mode 100644 new mode 100755 index 22bac4dcb..cae2dc8ef --- a/vitrage/tests/functional/api_handler/test_apis.py +++ b/vitrage/tests/functional/api_handler/test_apis.py @@ -26,6 +26,8 @@ from vitrage.datasources import NOVA_INSTANCE_DATASOURCE from vitrage.datasources import NOVA_ZONE_DATASOURCE from vitrage.datasources.transformer_base \ import create_cluster_placeholder_vertex +from vitrage.entity_graph.mappings.operational_alarm_severity import \ + OperationalAlarmSeverity from vitrage.graph.driver.networkx_graph import NXGraph import vitrage.graph.utils as graph_utils from vitrage.tests.unit.entity_graph.base import TestEntityGraphUnitBase @@ -61,6 +63,23 @@ class TestApis(TestEntityGraphUnitBase): self.assertEqual(2, len(alarms)) self._check_projects_entities(alarms, 'project_2', True) + def test_get_alarm_counts_with_not_admin_project(self): + # Setup + graph = self._create_graph() + apis = AlarmApis(graph, None) + ctx = {'tenant': 'project_2', 'is_admin': False} + + # Action + counts = apis.get_alarm_counts(ctx, all_tenants=False) + counts = json.loads(counts) + + # Test assertions + self.assertEqual(1, counts['WARNING']) + self.assertEqual(0, counts['SEVERE']) + self.assertEqual(1, counts['CRITICAL']) + self.assertEqual(0, counts['OK']) + self.assertEqual(0, counts['N/A']) + def test_get_alarms_with_all_tenants(self): # Setup graph = self._create_graph() @@ -75,6 +94,23 @@ class TestApis(TestEntityGraphUnitBase): self.assertEqual(5, len(alarms)) self._check_projects_entities(alarms, None, True) + def test_get_alarm_counts_with_all_tenants(self): + # Setup + graph = self._create_graph() + apis = AlarmApis(graph, None) + ctx = {'tenant': 'project_1', 'is_admin': False} + + # Action + counts = apis.get_alarm_counts(ctx, all_tenants=True) + counts = json.loads(counts) + + # Test assertions + self.assertEqual(2, counts['WARNING']) + self.assertEqual(2, counts['SEVERE']) + self.assertEqual(1, counts['CRITICAL']) + self.assertEqual(0, counts['OK']) + self.assertEqual(0, counts['N/A']) + def test_get_rca_with_admin_project(self): # Setup graph = self._create_graph() @@ -418,33 +454,43 @@ class TestApis(TestEntityGraphUnitBase): 'alarm_on_host', metadata={VProps.VITRAGE_TYPE: NOVA_HOST_DATASOURCE, VProps.NAME: 'host_1', - VProps.RESOURCE_ID: 'host_1'}) + VProps.RESOURCE_ID: 'host_1', + VProps.VITRAGE_OPERATIONAL_SEVERITY: + OperationalAlarmSeverity.SEVERE}) alarm_on_instance_1_vertex = self._create_alarm( 'alarm_on_instance_1', 'deduced_alarm', project_id='project_1', metadata={VProps.VITRAGE_TYPE: NOVA_INSTANCE_DATASOURCE, VProps.NAME: 'instance_1', - VProps.RESOURCE_ID: 'sdg7849ythksjdg'}) + VProps.RESOURCE_ID: 'sdg7849ythksjdg', + VProps.VITRAGE_OPERATIONAL_SEVERITY: + OperationalAlarmSeverity.SEVERE}) alarm_on_instance_2_vertex = self._create_alarm( 'alarm_on_instance_2', 'deduced_alarm', metadata={VProps.VITRAGE_TYPE: NOVA_INSTANCE_DATASOURCE, VProps.NAME: 'instance_2', - VProps.RESOURCE_ID: 'nbfhsdugf'}) + VProps.RESOURCE_ID: 'nbfhsdugf', + VProps.VITRAGE_OPERATIONAL_SEVERITY: + OperationalAlarmSeverity.WARNING}) alarm_on_instance_3_vertex = self._create_alarm( 'alarm_on_instance_3', 'deduced_alarm', project_id='project_2', metadata={VProps.VITRAGE_TYPE: NOVA_INSTANCE_DATASOURCE, VProps.NAME: 'instance_3', - VProps.RESOURCE_ID: 'nbffhsdasdugf'}) + VProps.RESOURCE_ID: 'nbffhsdasdugf', + VProps.VITRAGE_OPERATIONAL_SEVERITY: + OperationalAlarmSeverity.CRITICAL}) alarm_on_instance_4_vertex = self._create_alarm( 'alarm_on_instance_4', 'deduced_alarm', metadata={VProps.VITRAGE_TYPE: NOVA_INSTANCE_DATASOURCE, VProps.NAME: 'instance_4', - VProps.RESOURCE_ID: 'ngsuy76hgd87f'}) + VProps.RESOURCE_ID: 'ngsuy76hgd87f', + VProps.VITRAGE_OPERATIONAL_SEVERITY: + OperationalAlarmSeverity.WARNING}) # create links edges = list()