From d57af3234dfa0063f75eea2d1ddcd8dc19545db4 Mon Sep 17 00:00:00 2001 From: dongwenjuan Date: Mon, 12 Dec 2016 12:26:12 +0800 Subject: [PATCH] Implement aodh alarm notification Implement: blueprint aodh-message-bus-notifications Signed-off-by: dongwenjuan Change-Id: Ibd55345dea3b465006cdbd89bff2e67c3ac9daba --- devstack/README.rst | 11 +- devstack/gate_hook.sh | 6 + vitrage/datasources/alarm_driver_base.py | 30 ++- vitrage/datasources/alarm_transformer_base.py | 23 ++ vitrage/datasources/aodh/__init__.py | 2 +- vitrage/datasources/aodh/driver.py | 182 +++++++++++++- vitrage/datasources/aodh/properties.py | 9 + vitrage/datasources/aodh/transformer.py | 7 + vitrage/datasources/listener_service.py | 5 +- vitrage/datasources/transformer_base.py | 1 - .../evaluator/test_scenario_evaluator.py | 3 - vitrage/tests/mocks/mock_driver.py | 31 +++ vitrage/tests/mocks/mock_transformer.py | 30 +++ vitrage/tests/mocks/trace_generator.py | 19 +- .../driver/driver_aodh_update_dynamic.json | 11 + .../transformer_aodh_update_dynamic.json | 37 +++ .../aodh/aodh_transformer_base_test.py | 114 +++++++++ .../unit/datasources/aodh/mock_driver.py | 27 +++ .../unit/datasources/aodh/test_aodh_driver.py | 222 ++++++++++++++++++ .../datasources/aodh/test_aodh_transformer.py | 124 ++++------ .../datasources/nagios/test_nagios_driver.py | 6 - .../nagios/test_nagios_transformer.py | 11 +- .../datasources/zabbix/test_zabbix_driver.py | 6 - .../zabbix/test_zabbix_transformer.py | 10 +- 24 files changed, 793 insertions(+), 134 deletions(-) create mode 100644 vitrage/tests/resources/mock_configurations/driver/driver_aodh_update_dynamic.json create mode 100644 vitrage/tests/resources/mock_configurations/transformer/transformer_aodh_update_dynamic.json create mode 100644 vitrage/tests/unit/datasources/aodh/aodh_transformer_base_test.py create mode 100644 vitrage/tests/unit/datasources/aodh/mock_driver.py create mode 100644 vitrage/tests/unit/datasources/aodh/test_aodh_driver.py diff --git a/devstack/README.rst b/devstack/README.rst index 2292019f7..5d5f46410 100644 --- a/devstack/README.rst +++ b/devstack/README.rst @@ -53,4 +53,13 @@ Enabling Vitrage in DevStack notification_topics = notifications,vitrage_notifications notification_driver=messagingv2 -7. Run ``./stack.sh`` +7. Add this to add notification from aodh to vitrage + +.. code:: bash + + [[post-config|$AODH_CONF]] + [oslo_messaging_notifications] + driver = messagingv2 + topics = notifications,vitrage_notifications + +8. Run ``./stack.sh`` diff --git a/devstack/gate_hook.sh b/devstack/gate_hook.sh index 8c7f1044e..3b8147605 100644 --- a/devstack/gate_hook.sh +++ b/devstack/gate_hook.sh @@ -41,6 +41,7 @@ ENABLED_SERVICES+=,vitrage-api,vitrage-graph ENABLED_SERVICES+=,key,aodi-api,aodh-notifier,aodh-evaluator ENABLED_SERVICES+=,ceilometer-alarm-evaluator,ceilometer-alarm-notifier ENABLED_SERVICES+=,ceilometer-api +ENABLED_SERVICES+=,aodh-api export ENABLED_SERVICES @@ -69,6 +70,11 @@ notification_topics = notifications,vitrage_notifications notification_driver = messagingv2 policy_file = /etc/heat/policy.json-tempest +[[post-config|\$AODH_CONF]] +[oslo_messaging_notifications] +driver = messagingv2 +topics = notifications, vitrage_notifications + [[post-config|\$VITRAGE_CONF]] [static_physical] changes_interval = 5 diff --git a/vitrage/datasources/alarm_driver_base.py b/vitrage/datasources/alarm_driver_base.py index a660073a1..c1209dc33 100644 --- a/vitrage/datasources/alarm_driver_base.py +++ b/vitrage/datasources/alarm_driver_base.py @@ -85,15 +85,16 @@ class AlarmDriverBase(DriverBase): def _get_all_alarms(self): alarms = self._get_alarms() self._enrich_alarms(alarms) - return self._filter_and_cache_alarms(alarms, - AlarmDriverBase._filter_get_all) + return self._filter_and_cache_alarms( + alarms, + self._filter_get_erroneous) def _get_changed_alarms(self): alarms = self._get_alarms() self._enrich_alarms(alarms) return self._filter_and_cache_alarms( alarms, - AlarmDriverBase._filter_get_changes) + self._filter_get_change) def _filter_and_cache_alarms(self, alarms, filter_): alarms_to_update = [] @@ -101,16 +102,11 @@ class AlarmDriverBase(DriverBase): for alarm in alarms: alarm_key = self._alarm_key(alarm) - old_alarm, timestamp = self.cache.get(alarm_key, (None, None)) - - if filter_(self, alarm, old_alarm): - # delete state changed alarm: alarm->OK - if not self._is_erroneous(alarm): - alarm[DSProps.EVENT_TYPE] = GraphAction.DELETE_ENTITY + old_alarm = self.cache.get(alarm_key, (None, None))[0] + if self._filter_and_cache_alarm( + alarm, old_alarm, filter_, now): alarms_to_update.append(alarm) - self.cache[alarm_key] = alarm, now - # add alarms that were deleted values = list(self.cache.values()) for cached_alarm, timestamp in values: @@ -122,13 +118,16 @@ class AlarmDriverBase(DriverBase): return alarms_to_update - def _filter_get_all(self, alarm, old_alarm): + def _filter_get_valid(self, alarm, old_alarm): + return alarm if self._is_valid(alarm) else None + + def _filter_get_erroneous(self, alarm, old_alarm): return alarm \ if self._is_valid(alarm) and \ (self._is_erroneous(alarm) or self._is_erroneous(old_alarm)) \ else None - def _filter_get_changes(self, alarm, old_alarm): + def _filter_get_change(self, alarm, old_alarm): if not self._is_valid(alarm): return None if self._status_changed(alarm, old_alarm): @@ -137,3 +136,8 @@ class AlarmDriverBase(DriverBase): return alarm else: return None + + def _filter_and_cache_alarm(self, alarm, old_alarm, filter_, time): + ret = alarm if filter_(alarm, old_alarm) else None + self.cache[self._alarm_key(alarm)] = alarm, time + return ret diff --git a/vitrage/datasources/alarm_transformer_base.py b/vitrage/datasources/alarm_transformer_base.py index 560668435..73d32b3de 100644 --- a/vitrage/datasources/alarm_transformer_base.py +++ b/vitrage/datasources/alarm_transformer_base.py @@ -14,9 +14,11 @@ 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 EntityCategory from vitrage.common.constants import GraphAction +from vitrage.common.exception import VitrageTransformerError from vitrage.datasources.alarm_properties import AlarmProperties as AlarmProps from vitrage.datasources import transformer_base as tbase @@ -31,6 +33,27 @@ class AlarmTransformerBase(tbase.TransformerBase): def _ok_status(self, entity_event): pass + def _extract_graph_action(self, entity_event): + + if DSProps.EVENT_TYPE in entity_event and \ + entity_event[DSProps.EVENT_TYPE] == GraphAction.DELETE_ENTITY: + return entity_event[DSProps.EVENT_TYPE] + + datasource_action = entity_event[DSProps.DATASOURCE_ACTION] + + if datasource_action in \ + (DatasourceAction.UPDATE, DatasourceAction.SNAPSHOT): + return GraphAction.DELETE_ENTITY if self._ok_status(entity_event) else \ + self.GRAPH_ACTION_MAPPING.get( + entity_event.get(DSProps.EVENT_TYPE, None), + GraphAction.UPDATE_ENTITY) + + if DatasourceAction.INIT_SNAPSHOT == datasource_action: + return GraphAction.CREATE_ENTITY + + raise VitrageTransformerError('Invalid datasource action: (%s)' + % datasource_action) + def create_placeholder_vertex(self, **kwargs): LOG.info('An alarm cannot be a placeholder') pass diff --git a/vitrage/datasources/aodh/__init__.py b/vitrage/datasources/aodh/__init__.py index 2e3b4a651..0b1a01f06 100644 --- a/vitrage/datasources/aodh/__init__.py +++ b/vitrage/datasources/aodh/__init__.py @@ -28,7 +28,7 @@ OPTS = [ help='Aodh driver class path', required=True), cfg.StrOpt('update_method', - default=UpdateMethod.PULL, + default=UpdateMethod.PUSH, help='None: updates only via Vitrage periodic snapshots.' 'Pull: updates every [changes_interval] seconds.' 'Push: updates by getting notifications from the' diff --git a/vitrage/datasources/aodh/driver.py b/vitrage/datasources/aodh/driver.py index bdaf68446..75e33a98b 100644 --- a/vitrage/datasources/aodh/driver.py +++ b/vitrage/datasources/aodh/driver.py @@ -14,20 +14,27 @@ from oslo_log import log +from vitrage.common.constants import DatasourceAction +from vitrage.common.constants import DatasourceProperties as DSProps from vitrage.datasources.alarm_driver_base import AlarmDriverBase from vitrage.datasources.aodh import AODH_DATASOURCE +from vitrage.datasources.aodh.properties import AodhEventType from vitrage.datasources.aodh.properties import AodhProperties as AodhProps from vitrage.datasources.aodh.properties import AodhState from vitrage import os_clients +from vitrage.utils import datetime as datetime_utils LOG = log.getLogger(__name__) class AodhDriver(AlarmDriverBase): + def __init__(self, conf): super(AodhDriver, self).__init__() self._client = None self.conf = conf + self._init_aodh_event_actions() + self._cache_all_alarms() @property def client(self): @@ -41,12 +48,17 @@ class AodhDriver(AlarmDriverBase): def _alarm_key(self, alarm): return alarm[AodhProps.ALARM_ID] + def _cache_all_alarms(self): + alarms = self._get_alarms() + self._filter_and_cache_alarms(alarms, + self._filter_get_valid) + def _get_alarms(self): try: aodh_alarms = self.client.alarms.list() return [self._convert_alarm(alarm) for alarm in aodh_alarms] except Exception as e: - LOG.exception("Exception: %s", e) + LOG.exception("Failed to get all alarms, Exception: %s", e) return [] def _is_erroneous(self, alarm): @@ -104,7 +116,8 @@ class AodhDriver(AlarmDriverBase): @classmethod def _convert_alarm(cls, alarm): alarm_type = alarm.type - if alarm_type == AodhProps.EVENT and _is_vitrage_alarm(alarm): + if alarm_type == AodhProps.EVENT and \ + _is_vitrage_alarm(alarm.event_rule): return cls._convert_vitrage_alarm(alarm) elif alarm_type == AodhProps.EVENT: return cls._convert_event_alarm(alarm) @@ -113,6 +126,167 @@ class AodhDriver(AlarmDriverBase): else: LOG.warning('Unsupported Aodh alarm of type %s' % alarm_type) + @staticmethod + def get_event_types(): + # Add event_types to receive notifications about + return [AodhEventType.CREATION, + AodhEventType.STATE_TRANSITION, + AodhEventType.RULE_CHANGE, + AodhEventType.DELETION] + + def enrich_event(self, event, event_type): + if event_type in self.actions: + entity = self.actions[event_type](event) + else: + LOG.warning('Unsupported Aodh event type %s' % event_type) + return None + + # Don't need to update entity, only update the cache + if entity is None: + return None + + entity[DSProps.EVENT_TYPE] = event_type + + return AodhDriver.make_pickleable([entity], + AODH_DATASOURCE, + DatasourceAction.UPDATE)[0] + + def _init_aodh_event_actions(self): + self.actions = { + AodhEventType.CREATION: self._convert_alarm_creation_event, + AodhEventType.RULE_CHANGE: self._convert_alarm_rule_change_event, + AodhEventType.STATE_TRANSITION: + self._convert_alarm_state_transition_event, + AodhEventType.DELETION: self._convert_alarm_deletion_event + } + + @classmethod + def _convert_base_event(cls, event): + return { + AodhProps.PROJECT_ID: event[AodhProps.PROJECT_ID], + AodhProps.ALARM_ID: event[AodhProps.ALARM_ID], + AodhProps.SEVERITY: event[AodhProps.SEVERITY], + AodhProps.TIMESTAMP: event[AodhProps.TIMESTAMP], + } + + @classmethod + def _convert_vitrage_alarm_event(cls, rule): + return { + AodhProps.VITRAGE_ID: _parse_query(rule, AodhProps.VITRAGE_ID), + AodhProps.RESOURCE_ID: _parse_query(rule, AodhProps.RESOURCE_ID) + } + + @classmethod + def _convert_threshold_alarm_event(cls, event): + rule = event[AodhProps.DETAIL][AodhProps.RULE] + return { + AodhProps.RESOURCE_ID: _parse_query(rule, AodhProps.RESOURCE_ID), + AodhProps.STATE_TIMESTAMP: event[AodhProps.STATE_TIMESTAMP] + } + + @classmethod + def _convert_event_alarm_event(cls, rule): + return { + AodhProps.EVENT_TYPE: rule[AodhProps.EVENT_TYPE], + AodhProps.RESOURCE_ID: + _parse_query(rule, AodhProps.EVENT_RESOURCE_ID) + } + + @classmethod + def _convert_detail_event(cls, event): + alarm_info = event[AodhProps.DETAIL] + alarm_rule = alarm_info[AodhProps.RULE] + + entity_detail = { + AodhProps.DESCRIPTION: alarm_info[AodhProps.DESCRIPTION], + AodhProps.ENABLED: alarm_info[AodhProps.ENABLED], + AodhProps.NAME: alarm_info[AodhProps.NAME], + AodhProps.STATE: alarm_info[AodhProps.STATE], + AodhProps.REPEAT_ACTIONS: alarm_info[AodhProps.REPEAT_ACTIONS], + AodhProps.TYPE: alarm_info[AodhProps.TYPE] + } + + if _is_vitrage_alarm(alarm_rule): + entity_detail.update(cls._convert_vitrage_alarm_event(alarm_rule)) + elif entity_detail[AodhProps.TYPE] == AodhProps.EVENT: + entity_detail.update(cls._convert_event_alarm_event(alarm_rule)) + elif entity_detail[AodhProps.TYPE] == AodhProps.THRESHOLD: + entity_detail.update( + cls._convert_threshold_alarm_event(event)) + + return entity_detail + + @classmethod + def _parse_changed_rule(cls, change_rule): + entity = {} + if AodhProps.EVENT_TYPE in change_rule: + entity[AodhProps.EVENT_TYPE] = change_rule[AodhProps.EVENT_TYPE] + if 'query' in change_rule: + event_resource_id = \ + _parse_query(change_rule, AodhProps.EVENT_RESOURCE_ID) + resource_id = \ + _parse_query(change_rule, AodhProps.RESOURCE_ID) + if event_resource_id or resource_id: + entity[AodhProps.RESOURCE_ID] = event_resource_id if \ + event_resource_id is not None else resource_id + + return entity + + def _convert_alarm_creation_event(self, event): + entity = self._convert_base_event(event) + detail = self._convert_detail_event(event) + entity.update(detail) + + return self._filter_and_cache_alarm(entity, None, + self._filter_get_erroneous, + datetime_utils.utcnow(False)) + + def _convert_alarm_rule_change_event(self, event): + """handle alarm rule change notification + + example of changed rule: + "detail": {"severity": "critical", + "rule": + {"query": [{"field": "traits.resource_id", + "type": "", + "value": "1", + "op": "eq"}], + "event_type": "instance.update"}} + """ + + alarm_key = self._alarm_key(event) + old_alarm = self.cache.get(alarm_key, (None, None))[0] + entity = old_alarm.copy() + + changed_rule = event[AodhProps.DETAIL] + for (changed_type, changed_info) in changed_rule.items(): + # handle changed rule which may effect the neighbor + if changed_type == AodhProps.RULE: + entity.update(self._parse_changed_rule( + changed_rule[changed_type])) + # handle other changed alarm properties + elif changed_type in AodhProps.__dict__.values(): + entity[changed_type] = changed_info + + return self._filter_and_cache_alarm(entity, old_alarm, + self._filter_get_erroneous, + datetime_utils.utcnow(False)) + + def _convert_alarm_state_transition_event(self, event): + alarm_key = self._alarm_key(event) + old_alarm = self.cache.get(alarm_key, (None, None))[0] + entity = old_alarm.copy() + entity[AodhProps.STATE] = event[AodhProps.DETAIL][AodhProps.STATE] + + return self._filter_and_cache_alarm(entity, old_alarm, + self._filter_get_change, + datetime_utils.utcnow(False)) + + def _convert_alarm_deletion_event(self, event): + alarm_key = self._alarm_key(event) + alarm = self.cache.pop(alarm_key)[0] + return alarm if self._is_erroneous(alarm) else None + def _parse_query(data, key): query_fields = data.get(AodhProps.QUERY, {}) @@ -123,5 +297,5 @@ def _parse_query(data, key): return None -def _is_vitrage_alarm(alarm): - return _parse_query(alarm.event_rule, AodhProps.VITRAGE_ID) is not None +def _is_vitrage_alarm(rule): + return _parse_query(rule, AodhProps.VITRAGE_ID) is not None diff --git a/vitrage/datasources/aodh/properties.py b/vitrage/datasources/aodh/properties.py index e68021ca3..a2a8bdcb3 100644 --- a/vitrage/datasources/aodh/properties.py +++ b/vitrage/datasources/aodh/properties.py @@ -32,9 +32,18 @@ class AodhProperties(object): TIMESTAMP = 'timestamp' TYPE = 'type' VITRAGE_ID = 'vitrage_id' + DETAIL = 'detail' + RULE = 'rule' class AodhState(object): OK = 'ok' ALARM = 'alarm' INSUFFICIENT_DATA = 'insufficient_data' + + +class AodhEventType(object): + CREATION = 'alarm.creation' + RULE_CHANGE = 'alarm.rule_change' + STATE_TRANSITION = 'alarm.state_transition' + DELETION = 'alarm.deletion' diff --git a/vitrage/datasources/aodh/transformer.py b/vitrage/datasources/aodh/transformer.py index 3b27d85ff..c4aad9fbd 100644 --- a/vitrage/datasources/aodh/transformer.py +++ b/vitrage/datasources/aodh/transformer.py @@ -15,9 +15,11 @@ 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 VertexProperties as VProps from vitrage.datasources.alarm_transformer_base import AlarmTransformerBase from vitrage.datasources.aodh import AODH_DATASOURCE +from vitrage.datasources.aodh.properties import AodhEventType from vitrage.datasources.aodh.properties import AodhProperties as AodhProps from vitrage.datasources.aodh.properties import AodhState from vitrage.datasources import transformer_base as tbase @@ -28,6 +30,11 @@ from vitrage.utils import datetime as datetime_utils class AodhTransformer(AlarmTransformerBase): + # Event types which need to refer them differently + GRAPH_ACTION_MAPPING = { + AodhEventType.DELETION: GraphAction.DELETE_ENTITY, + } + def __init__(self, transformers, conf): super(AodhTransformer, self).__init__(transformers, conf) diff --git a/vitrage/datasources/listener_service.py b/vitrage/datasources/listener_service.py index 4838fd519..e889b5ada 100644 --- a/vitrage/datasources/listener_service.py +++ b/vitrage/datasources/listener_service.py @@ -95,5 +95,6 @@ class NotificationsEndpoint(object): def _enqueue_events(self, enriched_events): for event in enriched_events: - self.enqueue_callback(event) - LOG.debug('EVENT ENQUEUED: \n' + str(event)) + if event is not None: + self.enqueue_callback(event) + LOG.debug('EVENT ENQUEUED: \n' + str(event)) diff --git a/vitrage/datasources/transformer_base.py b/vitrage/datasources/transformer_base.py index 7488be3fb..16ac3cfa8 100644 --- a/vitrage/datasources/transformer_base.py +++ b/vitrage/datasources/transformer_base.py @@ -212,7 +212,6 @@ class TransformerBase(object): :return: the action that the processor needs to perform :rtype: str """ - if DSProps.EVENT_TYPE in entity_event and \ entity_event[DSProps.EVENT_TYPE] in GraphAction.__dict__.values(): return entity_event[DSProps.EVENT_TYPE] diff --git a/vitrage/tests/functional/evaluator/test_scenario_evaluator.py b/vitrage/tests/functional/evaluator/test_scenario_evaluator.py index c4301cfcd..67bd86b0d 100644 --- a/vitrage/tests/functional/evaluator/test_scenario_evaluator.py +++ b/vitrage/tests/functional/evaluator/test_scenario_evaluator.py @@ -19,7 +19,6 @@ from six.moves import queue from vitrage.common.constants import DatasourceAction from vitrage.common.constants import DatasourceProperties as DSProps from vitrage.common.constants import EdgeProperties as EProps -from vitrage.common.constants import GraphAction from vitrage.common.constants import VertexProperties as VProps from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE from vitrage.evaluator.scenario_evaluator import ScenarioEvaluator @@ -239,7 +238,6 @@ class TestScenarioEvaluator(TestFunctionalBase): # remove WARNING nagios alarm, leaving only CRITICAL one warning_test['status'] = 'OK' - warning_test[DSProps.EVENT_TYPE] = GraphAction.DELETE_ENTITY host_v = self.get_host_after_event(event_queue, warning_test, processor, _TARGET_HOST) alarms = \ @@ -251,7 +249,6 @@ class TestScenarioEvaluator(TestFunctionalBase): # next disable the alarm critical_test['status'] = 'OK' - critical_test[DSProps.EVENT_TYPE] = GraphAction.DELETE_ENTITY host_v = self.get_host_after_event(event_queue, critical_test, processor, _TARGET_HOST) alarms = \ diff --git a/vitrage/tests/mocks/mock_driver.py b/vitrage/tests/mocks/mock_driver.py index 3874840da..e6d0fccfe 100644 --- a/vitrage/tests/mocks/mock_driver.py +++ b/vitrage/tests/mocks/mock_driver.py @@ -435,3 +435,34 @@ def simple_zabbix_alarm_generators(host_num, }) return tg.get_trace_generators(test_entity_spec_list) + + +def simple_aodh_alarm_notification_generators(alarm_num, + update_events=0, + update_vals=None): + """A function for returning aodh alarm event generators. + + Returns generators for a given number of Aodh alarms. + + :param alarm_num: number of alarms + :param update_events: number of update alarms + :param update_vals: preset vals for ALL update events + :return: generators for alarm_num zones as specified + + Returns generators for a given number of alarms and + instances. + """ + + alarms = ['alarm-{0}'.format(index) for index in range(alarm_num)] + + test_entity_spec_list = [ + {tg.DYNAMIC_INFO_FKEY: tg.DRIVER_AODH_UPDATE_D, + tg.STATIC_INFO_FKEY: None, + tg.MAPPING_KEY: alarms, + tg.EXTERNAL_INFO_KEY: update_vals, + tg.NAME_KEY: 'Aodh update generator', + tg.NUM_EVENTS: update_events + } + ] + + return tg.get_trace_generators(test_entity_spec_list) diff --git a/vitrage/tests/mocks/mock_transformer.py b/vitrage/tests/mocks/mock_transformer.py index 488a75ac1..b13629cf3 100644 --- a/vitrage/tests/mocks/mock_transformer.py +++ b/vitrage/tests/mocks/mock_transformer.py @@ -158,3 +158,33 @@ def simple_aodh_alarm_generators(alarm_num, } ] return tg.get_trace_generators(test_entity_spec_list) + + +def simple_aodh_update_alarm_generators(alarm_num, + update_events=0, + update_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 update_events: number of update events + :param update_vals: values of update + :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_UPDATE_D, + tg.DYNAMIC_INFO_FPATH: tg.MOCK_TRANSFORMER_PATH, + tg.STATIC_INFO_FKEY: None, + tg.MAPPING_KEY: mapping, + tg.EXTERNAL_INFO_KEY: update_vals, + tg.NAME_KEY: 'Aodh update generator', + tg.NUM_EVENTS: update_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 b1a6e3ee7..6779197af 100644 --- a/vitrage/tests/mocks/trace_generator.py +++ b/vitrage/tests/mocks/trace_generator.py @@ -44,6 +44,7 @@ GENERATOR = 'generator' # Mock driver specs MOCK_DRIVER_PATH = '%s/mock_configurations/driver' % \ utils.get_resources_dir() +DRIVER_AODH_UPDATE_D = 'driver_aodh_update_dynamic.json' 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' @@ -64,6 +65,7 @@ DRIVER_ZONE_SNAPSHOT_D = 'driver_zone_snapshot_dynamic.json' MOCK_TRANSFORMER_PATH = '%s/mock_configurations/transformer' % \ utils.get_resources_dir() TRANS_AODH_SNAPSHOT_D = 'transformer_aodh_snapshot_dynamic.json' +TRANS_AODH_UPDATE_D = 'transformer_aodh_update_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' @@ -105,7 +107,8 @@ class EventTraceGenerator(object): """ static_info_parsers = \ - {DRIVER_INST_SNAPSHOT_D: _get_vm_snapshot_driver_values, + {DRIVER_AODH_UPDATE_D: _get_aodh_alarm_update_driver_values, + DRIVER_INST_SNAPSHOT_D: _get_vm_snapshot_driver_values, DRIVER_INST_UPDATE_D: _get_vm_update_driver_values, DRIVER_HOST_SNAPSHOT_D: _get_host_snapshot_driver_values, DRIVER_ZONE_SNAPSHOT_D: _get_zone_snapshot_driver_values, @@ -120,6 +123,7 @@ class EventTraceGenerator(object): _get_consistency_update_driver_values, TRANS_AODH_SNAPSHOT_D: _get_trans_aodh_alarm_snapshot_values, + TRANS_AODH_UPDATE_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} @@ -604,6 +608,19 @@ def _get_trans_aodh_alarm_snapshot_values(spec): return static_values +def _get_aodh_alarm_update_driver_values(spec): + alarms = 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 in alarms: + alarm_id = {"alarm_id": alarm} + static_values.append(combine_data( + static_info_re, alarm_id, 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/driver/driver_aodh_update_dynamic.json b/vitrage/tests/resources/mock_configurations/driver/driver_aodh_update_dynamic.json new file mode 100644 index 000000000..5da0c37ba --- /dev/null +++ b/vitrage/tests/resources/mock_configurations/driver/driver_aodh_update_dynamic.json @@ -0,0 +1,11 @@ +{ + "on_behalf_of": "c365d18fcc03493187016ae743f0cc4d", + "user_id": "3b0fd6ce33f24e38a4f415c380245e96", + "severity": "low", + "event_id": "e47602a8-cecd-4dd2-b50c-0f54ebca642a", + "timestamp": "2016-11-29T08:05:37.877253", + "alarm_id": "alarm-0", + "project_id": "c365d18fcc03493187016ae743f0cc4d", + "type": "", + "detail": {} +} diff --git a/vitrage/tests/resources/mock_configurations/transformer/transformer_aodh_update_dynamic.json b/vitrage/tests/resources/mock_configurations/transformer/transformer_aodh_update_dynamic.json new file mode 100644 index 000000000..409ce256c --- /dev/null +++ b/vitrage/tests/resources/mock_configurations/transformer/transformer_aodh_update_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": "update", + "vitrage_sample_date": "2016-11-29T06:31:50.094836", + "graph_query_result": [ + { + "category": "RESOURCE", + "is_placeholder": false, + "is_deleted": false, + "name": "test", + "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/aodh_transformer_base_test.py b/vitrage/tests/unit/datasources/aodh/aodh_transformer_base_test.py new file mode 100644 index 000000000..5e0ad2ea4 --- /dev/null +++ b/vitrage/tests/unit/datasources/aodh/aodh_transformer_base_test.py @@ -0,0 +1,114 @@ +# 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 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 VertexProperties as VProps +from vitrage.datasources.alarm_properties import AlarmProperties as AlarmProps +from vitrage.datasources.aodh.properties import AodhProperties as AodhProps +from vitrage.datasources.aodh.properties import AodhState +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 + + +class AodhTransformerBaseTest(base.BaseTest): + + 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) diff --git a/vitrage/tests/unit/datasources/aodh/mock_driver.py b/vitrage/tests/unit/datasources/aodh/mock_driver.py new file mode 100644 index 000000000..d6a03af1d --- /dev/null +++ b/vitrage/tests/unit/datasources/aodh/mock_driver.py @@ -0,0 +1,27 @@ +# 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 vitrage.datasources.aodh.driver import AodhDriver + + +class MockAodhDriver(AodhDriver): + """A aodh driver for tests. + + """ + + def __init__(self, conf): + super(MockAodhDriver, self).__init__(conf) + + def _cache_all_alarms(self): + pass diff --git a/vitrage/tests/unit/datasources/aodh/test_aodh_driver.py b/vitrage/tests/unit/datasources/aodh/test_aodh_driver.py new file mode 100644 index 000000000..a6f13ff9f --- /dev/null +++ b/vitrage/tests/unit/datasources/aodh/test_aodh_driver.py @@ -0,0 +1,222 @@ +# 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 DatasourceProperties as DSProps +from vitrage.common.constants import UpdateMethod +from vitrage.datasources.aodh import AODH_DATASOURCE +from vitrage.datasources.aodh.properties import AodhEventType +from vitrage.datasources.aodh.properties import AodhProperties as AodhProps +from vitrage.tests import base +from vitrage.tests.mocks import mock_driver +from vitrage.tests.unit.datasources.aodh.mock_driver import MockAodhDriver + +LOG = logging.getLogger(__name__) + + +class AodhDriverTest(base.BaseTest): + + OPTS = [ + cfg.StrOpt('update_method', + default=UpdateMethod.PUSH), + ] + + # noinspection PyPep8Naming + @classmethod + def setUpClass(cls): + cls.conf = cfg.ConfigOpts() + cls.conf.register_opts(cls.OPTS, group=AODH_DATASOURCE) + + def test_enrich_event(self): + + aodh_driver = MockAodhDriver(self.conf) + + # 1. alarm creation with 'ok' state + # prepare data + detail_data = {"type": "creation", + AodhProps.DETAIL: self._extract_alarm_data()} + generators = \ + mock_driver.simple_aodh_alarm_notification_generators( + alarm_num=1, + update_events=1, + update_vals=detail_data) + alarm = mock_driver.generate_sequential_events_list(generators)[0] + alarm_info = alarm.copy() + + # action + entity = aodh_driver.enrich_event(alarm, AodhEventType.CREATION) + + # Test assertions + # alarm with status OK should not be handled + self.assertIsNone(entity) + + # 2.alarm state transition from 'ok' to 'alarm' + detail_data = {"type": "state transition", + AodhProps.DETAIL: {AodhProps.STATE: "alarm"}} + alarm.update(detail_data) + entity = aodh_driver.enrich_event(alarm, + AodhEventType.STATE_TRANSITION) + + # Test assertions + # alarm state change: ok->alarm, need to be added + self.assertIsNotNone(entity) + self._validate_aodh_entity_comm_props(entity, alarm_info) + self.assertEqual(entity[AodhProps.STATE], + alarm[AodhProps.DETAIL][AodhProps.STATE]) + self.assertEqual(entity[AodhProps.SEVERITY], + alarm[AodhProps.SEVERITY]) + self.assertEqual(entity[DSProps.EVENT_TYPE], + AodhEventType.STATE_TRANSITION) + + # 3. delete alarm which is 'alarm' state + # prepare data + detail_data = {"type": "deletion"} + alarm.update(detail_data) + + # action + entity = aodh_driver.enrich_event(alarm, AodhEventType.DELETION) + + # Test assertions + self.assertIsNotNone(entity) + self._validate_aodh_entity_comm_props(entity, alarm_info) + self.assertEqual(entity[DSProps.EVENT_TYPE], + AodhEventType.DELETION) + + # 4. alarm creation with 'alarm' state + # prepare data + detail_data = {"type": "creation", + AodhProps.DETAIL: + self._extract_alarm_data(state="alarm")} + generators = \ + mock_driver.simple_aodh_alarm_notification_generators( + alarm_num=1, + update_events=1, + update_vals=detail_data) + alarm = mock_driver.generate_sequential_events_list(generators)[0] + alarm_info = alarm.copy() + + # action + entity = aodh_driver.enrich_event(alarm, AodhEventType.CREATION) + + # Test assertions + # alarm with status 'alarm' need to be added + self.assertIsNotNone(entity) + self._validate_aodh_entity_comm_props(entity, alarm_info) + self.assertEqual(entity[AodhProps.STATE], + alarm[AodhProps.DETAIL][AodhProps.STATE]) + self.assertEqual(entity[AodhProps.SEVERITY], + alarm[AodhProps.SEVERITY]) + self.assertIsNone(entity[AodhProps.RESOURCE_ID]) + self.assertEqual(entity[AodhProps.EVENT_TYPE], "*") + self.assertEqual(entity[DSProps.EVENT_TYPE], + AodhEventType.CREATION) + + # 5. alarm rule change + # prepare data + detail_data = {"type": "rule change", + AodhProps.DETAIL: { + "severity": "critical", + AodhProps.RULE: + {"query": [{"field": "traits.resource_id", + "type": "", + "value": "1", + "op": "eq"}], + "event_type": "instance.update"}}} + alarm.update(detail_data) + + # action + entity = aodh_driver.enrich_event(alarm, + AodhEventType.RULE_CHANGE) + + # Test assertions + # alarm rule change: need to be update + self.assertIsNotNone(entity) + self._validate_aodh_entity_comm_props(entity, alarm_info) + self.assertEqual(entity[AodhProps.SEVERITY], + alarm[AodhProps.DETAIL][AodhProps.SEVERITY]) + self.assertEqual( + entity[AodhProps.EVENT_TYPE], + alarm[AodhProps.DETAIL][AodhProps.RULE][AodhProps.EVENT_TYPE]) + self.assertEqual(entity[AodhProps.RESOURCE_ID], + "1") + self.assertEqual(entity[DSProps.EVENT_TYPE], + AodhEventType.RULE_CHANGE) + + # 6. alarm state change from 'alarm' to 'ok' + # prepare data + detail_data = {"type": "state transition", + AodhProps.DETAIL: {AodhProps.STATE: "ok"}} + alarm.update(detail_data) + + # action + entity = aodh_driver.enrich_event(alarm, + AodhEventType.STATE_TRANSITION) + + # Test assertions + # alarm state change: alarm->OK, need to be deleted + self.assertIsNotNone(entity) + self._validate_aodh_entity_comm_props(entity, alarm_info) + self.assertEqual(entity[DSProps.EVENT_TYPE], + AodhEventType.STATE_TRANSITION) + + # 7. delete alarm which is 'ok' state + # prepare data + detail_data = {"type": "deletion"} + alarm.update(detail_data) + + # action + entity = aodh_driver.enrich_event(alarm, AodhEventType.DELETION) + + # Test assertions + self.assertIsNone(entity) + + def _extract_alarm_data(self, + state="ok", + type="event", + rule={"query": [], + "event_type": "*"}): + + return {AodhProps.DESCRIPTION: "test", + AodhProps.TIMESTAMP: "2016-11-09T01:39:13.839584", + AodhProps.ENABLED: True, + AodhProps.STATE_TIMESTAMP: "2016-11-09T01:39:13.839584", + AodhProps.ALARM_ID: "7e5c3754-e2eb-4782-ae00-7da5ded8568b", + AodhProps.REPEAT_ACTIONS: False, + AodhProps.PROJECT_ID: "c365d18fcc03493187016ae743f0cc4d", + AodhProps.NAME: "test", + AodhProps.SEVERITY: "low", + AodhProps.TYPE: type, + AodhProps.STATE: state, + AodhProps.RULE: rule} + + def _validate_aodh_entity_comm_props(self, entity, alarm): + + self.assertEqual(entity[AodhProps.ALARM_ID], + alarm[AodhProps.ALARM_ID]) + self.assertEqual(entity[AodhProps.PROJECT_ID], + alarm[AodhProps.PROJECT_ID]) + self.assertEqual(entity[AodhProps.TIMESTAMP], + alarm[AodhProps.TIMESTAMP]) + self.assertEqual(entity[AodhProps.DESCRIPTION], + alarm[AodhProps.DETAIL][AodhProps.DESCRIPTION]) + self.assertEqual(entity[AodhProps.ENABLED], + alarm[AodhProps.DETAIL][AodhProps.ENABLED]) + self.assertEqual(entity[AodhProps.NAME], + alarm[AodhProps.DETAIL][AodhProps.NAME]) + self.assertEqual(entity[AodhProps.REPEAT_ACTIONS], + alarm[AodhProps.DETAIL][AodhProps.REPEAT_ACTIONS]) + self.assertEqual(entity[AodhProps.TYPE], + alarm[AodhProps.DETAIL][AodhProps.TYPE]) diff --git a/vitrage/tests/unit/datasources/aodh/test_aodh_transformer.py b/vitrage/tests/unit/datasources/aodh/test_aodh_transformer.py index 44caaf40f..610bca42f 100644 --- a/vitrage/tests/unit/datasources/aodh/test_aodh_transformer.py +++ b/vitrage/tests/unit/datasources/aodh/test_aodh_transformer.py @@ -15,32 +15,25 @@ 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 +from vitrage.tests.unit.datasources.aodh.aodh_transformer_base_test import \ + AodhTransformerBaseTest LOG = logging.getLogger(__name__) -class TestAodhAlarmTransformer(base.BaseTest): +class TestAodhAlarmTransformer(AodhTransformerBaseTest): OPTS = [ cfg.StrOpt('update_method', - default=UpdateMethod.PUSH), + default=UpdateMethod.PULL), ] @classmethod @@ -112,86 +105,49 @@ class TestAodhAlarmTransformer(base.BaseTest): 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]) +class TestAodhAlarmPushTransformer(AodhTransformerBaseTest): - 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]) + OPTS = [ + cfg.StrOpt('update_method', + default=UpdateMethod.PUSH), + ] - 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 + @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) - 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 test_update_transform(self): + LOG.debug('Aodh update alarm transformer test:' + 'transform entity event update') - def _validate_neighbors(self, neighbors, alarm_id, event): - resource_counter = 0 + # Test setup + spec_list = \ + mock_sync.simple_aodh_update_alarm_generators(alarm_num=5, + update_events=5) + static_events = mock_sync.generate_random_events_list(spec_list) - for neighbor in neighbors: - resource_id = event[AodhProps.RESOURCE_ID] - self._validate_instance_neighbor(neighbor, - resource_id, - alarm_id) - resource_counter += 1 + 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 - self.assertEqual(1, - resource_counter, - 'Alarm can be belonged to only one resource') + # Test action + wrapper = self.transformers[AODH_DATASOURCE].transform(event) - 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]) + # Test assertions + vertex = wrapper.vertex + self._validate_aodh_vertex_props(vertex, event) - # 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) + neighbors = wrapper.neighbors + self.assertEqual(1, len(neighbors)) + self._validate_neighbors(neighbors, vertex.vertex_id, event) - 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) + self._validate_action(event, wrapper) diff --git a/vitrage/tests/unit/datasources/nagios/test_nagios_driver.py b/vitrage/tests/unit/datasources/nagios/test_nagios_driver.py index 2c16fcaa6..6b30f1f17 100644 --- a/vitrage/tests/unit/datasources/nagios/test_nagios_driver.py +++ b/vitrage/tests/unit/datasources/nagios/test_nagios_driver.py @@ -144,9 +144,6 @@ class NagiosDriverTest(NagiosBaseTest): self.assertEqual(2, len(services)) self._assert_contains(service_data1, services) self._assert_contains(service_data2, services) - for service in services: - self.assertEqual(GraphAction.DELETE_ENTITY, - service[DSProps.EVENT_TYPE]) # Action services = nagios_driver._get_all_alarms() @@ -278,9 +275,6 @@ class NagiosDriverTest(NagiosBaseTest): self.assertEqual(2, len(services)) self._assert_contains(service_data1, services) self._assert_contains(service_data2, services) - for service in services: - self.assertEqual(GraphAction.DELETE_ENTITY, - service[DSProps.EVENT_TYPE]) # Action services = nagios_driver._get_changed_alarms() diff --git a/vitrage/tests/unit/datasources/nagios/test_nagios_transformer.py b/vitrage/tests/unit/datasources/nagios/test_nagios_transformer.py index 2f59f237a..ef3c62dc0 100644 --- a/vitrage/tests/unit/datasources/nagios/test_nagios_transformer.py +++ b/vitrage/tests/unit/datasources/nagios/test_nagios_transformer.py @@ -90,6 +90,7 @@ class NagiosTransformerTest(base.BaseTest): # Test action wrapper = NagiosTransformer(self.transformers, self.conf).\ transform(alarm) + self._validate_vertex(wrapper.vertex, alarm) neighbors = wrapper.neighbors @@ -103,14 +104,12 @@ class NagiosTransformerTest(base.BaseTest): self._validate_action(alarm, wrapper) 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) + if alarm[NagiosProperties.STATUS] == 'OK': + self.assertEqual(GraphAction.DELETE_ENTITY, wrapper.action) + else: + self.assertEqual(GraphAction.UPDATE_ENTITY, wrapper.action) else: self.assertEqual(GraphAction.CREATE_ENTITY, wrapper.action) diff --git a/vitrage/tests/unit/datasources/zabbix/test_zabbix_driver.py b/vitrage/tests/unit/datasources/zabbix/test_zabbix_driver.py index 263a3f8f5..c4f3bcf99 100644 --- a/vitrage/tests/unit/datasources/zabbix/test_zabbix_driver.py +++ b/vitrage/tests/unit/datasources/zabbix/test_zabbix_driver.py @@ -143,9 +143,6 @@ class ZabbixDriverTest(ZabbixBaseTest): self.assertEqual(2, len(alarms)) self._assert_contains(expected_alarm1, alarms) self._assert_contains(expected_alarm2, alarms) - for alarm in alarms: - self.assertEqual(GraphAction.DELETE_ENTITY, - alarm[DSProps.EVENT_TYPE]) # Step 4 - get all when all alarms are inactivated and their status # was not changed @@ -254,9 +251,6 @@ class ZabbixDriverTest(ZabbixBaseTest): self.assertEqual(2, len(alarms)) self._assert_contains(expected_alarm1, alarms) self._assert_contains(expected_alarm2, alarms) - for alarm in alarms: - self.assertEqual(GraphAction.DELETE_ENTITY, - alarm[DSProps.EVENT_TYPE]) # Step 6 - get changes when no change occurred # Action diff --git a/vitrage/tests/unit/datasources/zabbix/test_zabbix_transformer.py b/vitrage/tests/unit/datasources/zabbix/test_zabbix_transformer.py index a19381f8c..002358996 100644 --- a/vitrage/tests/unit/datasources/zabbix/test_zabbix_transformer.py +++ b/vitrage/tests/unit/datasources/zabbix/test_zabbix_transformer.py @@ -108,14 +108,12 @@ class ZabbixTransformerTest(base.BaseTest): self._validate_action(alarm, wrapper) 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) + if alarm[ZabbixProps.VALUE] == ZabbixTriggerValue.OK: + self.assertEqual(GraphAction.DELETE_ENTITY, wrapper.action) + else: + self.assertEqual(GraphAction.UPDATE_ENTITY, wrapper.action) else: self.assertEqual(GraphAction.CREATE_ENTITY, wrapper.action)