diff --git a/vitrage/tests/mocks/mock_transformer.py b/vitrage/tests/mocks/mock_transformer.py index 5f017ec1b..488a75ac1 100644 --- a/vitrage/tests/mocks/mock_transformer.py +++ b/vitrage/tests/mocks/mock_transformer.py @@ -129,3 +129,32 @@ def simple_zone_generators(zone_num, snapshot_events=0, snap_vals=None): } ] return tg.get_trace_generators(test_entity_spec_list) + + +def simple_aodh_alarm_generators(alarm_num, + snapshot_events=0, snap_vals=None): + """A simple function for returning aodh alarm generators. + + Returns generators for a given number of alarms. + + :param alarm_num: number of alarms + :param snapshot_events: number of snapshot events + :param snap_vals: values of snapshot + :return: generators for alarm_num alarms as specified + """ + + mapping = [('alarm-{0}'.format(ind), 'resource-{0}'.format(ind)) + for ind in range(alarm_num) + ] + + test_entity_spec_list = [ + {tg.DYNAMIC_INFO_FKEY: tg.TRANS_AODH_SNAPSHOT_D, + tg.DYNAMIC_INFO_FPATH: tg.MOCK_TRANSFORMER_PATH, + tg.STATIC_INFO_FKEY: None, + tg.MAPPING_KEY: mapping, + tg.EXTERNAL_INFO_KEY: snap_vals, + tg.NAME_KEY: 'Aodh snapshot generator', + tg.NUM_EVENTS: snapshot_events + } + ] + return tg.get_trace_generators(test_entity_spec_list) diff --git a/vitrage/tests/mocks/trace_generator.py b/vitrage/tests/mocks/trace_generator.py index 080b54a11..b1a6e3ee7 100644 --- a/vitrage/tests/mocks/trace_generator.py +++ b/vitrage/tests/mocks/trace_generator.py @@ -30,6 +30,7 @@ from vitrage.tests.mocks.entity_model import BasicEntityModel as Bem import vitrage.tests.mocks.utils as utils DYNAMIC_INFO_FKEY = 'filename' +DYNAMIC_INFO_FPATH = 'filepath' STATIC_INFO_FKEY = 'static_filename' NAME_KEY = 'name' MAPPING_KEY = 'mapping' @@ -41,6 +42,8 @@ GENERATOR = 'generator' # specification files for input types # Mock driver specs +MOCK_DRIVER_PATH = '%s/mock_configurations/driver' % \ + utils.get_resources_dir() DRIVER_HOST_SNAPSHOT_D = 'driver_host_snapshot_dynamic.json' DRIVER_INST_SNAPSHOT_D = 'driver_inst_snapshot_dynamic.json' DRIVER_INST_SNAPSHOT_S = 'driver_inst_snapshot_static.json' @@ -58,6 +61,9 @@ DRIVER_ZONE_SNAPSHOT_D = 'driver_zone_snapshot_dynamic.json' # Mock transformer Specs (i.e., what the transformer outputs) +MOCK_TRANSFORMER_PATH = '%s/mock_configurations/transformer' % \ + utils.get_resources_dir() +TRANS_AODH_SNAPSHOT_D = 'transformer_aodh_snapshot_dynamic.json' TRANS_INST_SNAPSHOT_D = 'transformer_inst_snapshot_dynamic.json' TRANS_INST_SNAPSHOT_S = 'transformer_inst_snapshot_static.json' TRANS_HOST_SNAPSHOT_D = 'transformer_host_snapshot_dynamic.json' @@ -113,11 +119,15 @@ class EventTraceGenerator(object): DRIVER_CONSISTENCY_UPDATE_D: _get_consistency_update_driver_values, + TRANS_AODH_SNAPSHOT_D: _get_trans_aodh_alarm_snapshot_values, TRANS_INST_SNAPSHOT_D: _get_trans_vm_snapshot_values, TRANS_HOST_SNAPSHOT_D: _get_trans_host_snapshot_values, TRANS_ZONE_SNAPSHOT_D: _get_trans_zone_snapshot_values} - dynam_specs = utils.load_specs(spec[DYNAMIC_INFO_FKEY]) + target_folder = spec[DYNAMIC_INFO_FPATH] \ + if spec.get(DYNAMIC_INFO_FPATH) else None + dynam_specs = utils.load_specs(spec[DYNAMIC_INFO_FKEY], + target_folder=target_folder) dynamic_spec_filename = spec[DYNAMIC_INFO_FKEY].split('/')[-1] static_specs = static_info_parsers[dynamic_spec_filename](spec) self.name = spec.get(NAME_KEY, 'generator') @@ -569,6 +579,31 @@ def _get_trans_zone_snapshot_values(spec): return static_values +def _get_trans_aodh_alarm_snapshot_values(spec): + """Generates the static transformer values for each vm. + + :param spec: specification of event generation. + :type spec: dict + :return: list of static transformer values for each vm. + :rtype: list + """ + + alarm_resources_mapping = spec[MAPPING_KEY] + static_info_re = None + if spec[STATIC_INFO_FKEY] is not None: + static_info_re = utils.load_specs(spec[STATIC_INFO_FKEY]) + + static_values = [] + for alarm_id, resource_id in alarm_resources_mapping: + mapping = {'alarm_id': alarm_id, + 'resource_id': resource_id, + 'graph_query_result': [{'id': resource_id}]} + static_values.append(combine_data( + static_info_re, mapping, spec.get(EXTERNAL_INFO_KEY, None) + )) + return static_values + + def combine_data(static_info_re, mapping_info, external_info): if external_info: mapping_info = utils.merge_vals(mapping_info, external_info) diff --git a/vitrage/tests/resources/mock_configurations/transformer/transformer_aodh_snapshot_dynamic.json b/vitrage/tests/resources/mock_configurations/transformer/transformer_aodh_snapshot_dynamic.json new file mode 100644 index 000000000..1e956aa12 --- /dev/null +++ b/vitrage/tests/resources/mock_configurations/transformer/transformer_aodh_snapshot_dynamic.json @@ -0,0 +1,37 @@ +{ + "alarm_id": "21127fbd-7633-494e-8e5c-fdf415cdec0c", + "description": "test", + "enabled": "True", + "name": "test", + "project_id": "f0895991f44044ccba8e62b201b70360", + "repeat_actions": "False", + "severity": "low", + "state": "alarm", + "timestamp": "2016-11-29T06:31:50.094836", + "state_timestamp": "2016-11-11T01:58:36.054090", + "type": "event", + "event_type": "compute.instance.*", + "resource_id": "3dcee183-ca42-4ccb-84af-9f0196b2e160", + "vitrage_event_type": "alarm.creation", + "vitrage_entity_type": "aodh", + "vitrage_datasource_action": "snapshot", + "vitrage_sample_date": "2016-11-29T06:31:50.094836", + "graph_query_result": [ + { + "category": "RESOURCE", + "is_placeholder": false, + "is_deleted": false, + "name": "dwj", + "update_timestamp": "2016-12-02 07:18:05.628479+00:00", + "sample_timestamp": "2016-12-02 07:18:05.628479+00:00", + "operational_state": "OK", + "aggregated_state": "ACTIVE", + "state": "ACTIVE", + "graph_index": 3, + "project_id": "f0895991f44044ccba8e62b201b70360", + "type": "nova.instance", + "id": "3dcee183-ca42-4ccb-84af-9f0196b2e160" + } + ] +} + diff --git a/vitrage/tests/unit/datasources/aodh/__init__.py b/vitrage/tests/unit/datasources/aodh/__init__.py new file mode 100644 index 000000000..d4235c254 --- /dev/null +++ b/vitrage/tests/unit/datasources/aodh/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2016 - ZTE +# +# 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' diff --git a/vitrage/tests/unit/datasources/aodh/test_aodh_transformer.py b/vitrage/tests/unit/datasources/aodh/test_aodh_transformer.py new file mode 100644 index 000000000..44caaf40f --- /dev/null +++ b/vitrage/tests/unit/datasources/aodh/test_aodh_transformer.py @@ -0,0 +1,197 @@ +# Copyright 2016 - ZTE, 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_config import cfg +from oslo_log import log as logging + +from vitrage.common.constants import DatasourceAction +from vitrage.common.constants import DatasourceProperties as DSProps +from vitrage.common.constants import EdgeLabel +from vitrage.common.constants import EntityCategory +from vitrage.common.constants import GraphAction +from vitrage.common.constants import UpdateMethod +from vitrage.common.constants import VertexProperties as VProps +from vitrage.datasources.alarm_properties import AlarmProperties as AlarmProps +from vitrage.datasources.aodh import AODH_DATASOURCE +from vitrage.datasources.aodh.properties import AodhProperties as AodhProps +from vitrage.datasources.aodh.properties import AodhState +from vitrage.datasources.aodh.transformer import AodhTransformer +from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE +from vitrage.datasources.transformer_base import TransformerBase +from vitrage.graph.driver.elements import Vertex +from vitrage.tests import base +from vitrage.tests.mocks import mock_transformer as mock_sync + +LOG = logging.getLogger(__name__) + + +class TestAodhAlarmTransformer(base.BaseTest): + + OPTS = [ + cfg.StrOpt('update_method', + default=UpdateMethod.PUSH), + ] + + @classmethod + def setUpClass(cls): + cls.transformers = {} + cls.conf = cfg.ConfigOpts() + cls.conf.register_opts(cls.OPTS, group=AODH_DATASOURCE) + cls.transformers[AODH_DATASOURCE] = \ + AodhTransformer(cls.transformers, cls.conf) + + def test_key_values_with_vitrage_alarm(self): + LOG.debug('Aodh transformer test: get key values(vitrage_alarm)') + + # Test setup + entity = {AodhProps.VITRAGE_ID: 'test', + DSProps.ENTITY_TYPE: AODH_DATASOURCE, + AodhProps.ALARM_ID: '12345'} + transformer = self.transformers[AODH_DATASOURCE] + + # Test action + observed_key_fields = transformer._create_entity_key(entity) + + # Test assertions + self.assertEqual('test', observed_key_fields) + + def test_key_values(self): + LOG.debug('Aodh transformer test: get key values(aodh alarm)') + + # Test setup + entity = {DSProps.ENTITY_TYPE: AODH_DATASOURCE, + AodhProps.ALARM_ID: '12345'} + transformer = self.transformers[AODH_DATASOURCE] + + # Test action + entity_key_fields = transformer._create_entity_key(entity).split(":") + + # Test assertions + self.assertEqual(EntityCategory.ALARM, entity_key_fields[0]) + self.assertEqual(AODH_DATASOURCE, entity_key_fields[1]) + self.assertEqual(entity[AodhProps.ALARM_ID], entity_key_fields[2]) + + def test_snapshot_transform(self): + LOG.debug('Aodh alarm transformer test: transform entity event ' + 'snapshot') + + # Test setup + spec_list = mock_sync.simple_aodh_alarm_generators(alarm_num=3, + snapshot_events=3) + static_events = mock_sync.generate_random_events_list(spec_list) + + for event in static_events: + # convert neighbor from dict to vertex object + neighbors = event[TransformerBase.QUERY_RESULT] + vertices = [] + for neighbor in neighbors: + vertices.append(self._convert_dist_to_vertex(neighbor)) + event[TransformerBase.QUERY_RESULT] = vertices + + # Test action + wrapper = self.transformers[AODH_DATASOURCE].transform(event) + + # Test assertions + vertex = wrapper.vertex + self._validate_aodh_vertex_props(vertex, event) + + neighbors = wrapper.neighbors + self.assertEqual(1, len(neighbors)) + self._validate_neighbors(neighbors, vertex.vertex_id, event) + + self._validate_action(event, wrapper) + + def _validate_aodh_vertex_props(self, vertex, event): + + self.assertEqual(EntityCategory.ALARM, vertex[VProps.CATEGORY]) + self.assertEqual(event[DSProps.ENTITY_TYPE], vertex[VProps.TYPE]) + self.assertEqual(event[AodhProps.NAME], vertex[VProps.NAME]) + self.assertEqual(event[AodhProps.SEVERITY], vertex[VProps.SEVERITY]) + self.assertEqual(event[AodhProps.DESCRIPTION], + vertex[AodhProps.DESCRIPTION]) + self.assertEqual(event[AodhProps.ENABLED], vertex[AodhProps.ENABLED]) + self.assertEqual(event[AodhProps.PROJECT_ID], + vertex[VProps.PROJECT_ID]) + self.assertEqual(event[AodhProps.REPEAT_ACTIONS], + vertex[AodhProps.REPEAT_ACTIONS]) + self.assertEqual(event[AodhProps.TYPE], vertex['alarm_type']) + if event[AodhProps.TYPE] == AodhProps.EVENT: + self.assertEqual(event[AodhProps.EVENT_TYPE], + vertex[AodhProps.EVENT_TYPE]) + elif event[AodhProps.TYPE] == AodhProps.THRESHOLD: + self.assertEqual(event[AodhProps.STATE_TIMESTAMP], + vertex[AodhProps.STATE_TIMESTAMP]) + self.assertEqual(event[DSProps.SAMPLE_DATE], + vertex[VProps.SAMPLE_TIMESTAMP]) + + event_status = event[AodhProps.STATE] + if event_status == AodhState.OK: + self.assertEqual(AlarmProps.INACTIVE_STATE, + vertex[VProps.STATE]) + else: + self.assertEqual(AlarmProps.ACTIVE_STATE, + vertex[VProps.STATE]) + self.assertFalse(vertex[VProps.IS_PLACEHOLDER]) + self.assertFalse(vertex[VProps.IS_DELETED]) + + def _validate_action(self, alarm, wrapper): + if DSProps.EVENT_TYPE in alarm \ + and alarm[DSProps.EVENT_TYPE] in GraphAction.__dict__.values(): + self.assertEqual(alarm[DSProps.EVENT_TYPE], wrapper.action) + return + + ds_action = alarm[DSProps.DATASOURCE_ACTION] + if ds_action in (DatasourceAction.SNAPSHOT, DatasourceAction.UPDATE): + self.assertEqual(GraphAction.UPDATE_ENTITY, wrapper.action) + else: + self.assertEqual(GraphAction.CREATE_ENTITY, wrapper.action) + + def _validate_neighbors(self, neighbors, alarm_id, event): + resource_counter = 0 + + for neighbor in neighbors: + resource_id = event[AodhProps.RESOURCE_ID] + self._validate_instance_neighbor(neighbor, + resource_id, + alarm_id) + resource_counter += 1 + + self.assertEqual(1, + resource_counter, + 'Alarm can be belonged to only one resource') + + def _validate_instance_neighbor(self, + alarm_neighbor, + resource_id, + alarm_vertex_id): + # validate neighbor vertex + self.assertEqual(EntityCategory.RESOURCE, + alarm_neighbor.vertex[VProps.CATEGORY]) + self.assertEqual(NOVA_INSTANCE_DATASOURCE, + alarm_neighbor.vertex[VProps.TYPE]) + self.assertEqual(resource_id, alarm_neighbor.vertex[VProps.ID]) + self.assertFalse(alarm_neighbor.vertex[VProps.IS_PLACEHOLDER]) + self.assertFalse(alarm_neighbor.vertex[VProps.IS_DELETED]) + + # Validate neighbor edge + edge = alarm_neighbor.edge + self.assertEqual(edge.target_id, alarm_neighbor.vertex.vertex_id) + self.assertEqual(edge.source_id, alarm_vertex_id) + self.assertEqual(edge.label, EdgeLabel.ON) + + def _convert_dist_to_vertex(self, neighbor): + ver_id = neighbor[VProps.CATEGORY] + \ + TransformerBase.KEY_SEPARATOR + neighbor[VProps.TYPE] + \ + TransformerBase.KEY_SEPARATOR + neighbor[VProps.ID] + return Vertex(vertex_id=ver_id, properties=neighbor)