From 4ec0e3a8f8b5dae26064611a540d2a0126026c2f Mon Sep 17 00:00:00 2001 From: Idan Hefetz Date: Tue, 12 Apr 2016 11:21:03 +0000 Subject: [PATCH] deduced alarm notifier, is subscribed to graph changes, upon a deduced alarm added an activate notification is sent to the notifers service Change-Id: I4b51ea0a1f0db8c42ab8d2c9497ebecebe6d07a0 --- vitrage/datasources/aodh/driver.py | 101 +++++++------- vitrage/datasources/aodh/properties.py | 1 + vitrage/datasources/aodh/transformer.py | 39 +++++- vitrage/entity_graph/__init__.py | 4 + vitrage/entity_graph/processor/notifier.py | 95 +++++++++++++ vitrage/entity_graph/processor/processor.py | 18 ++- vitrage/graph/driver/networkx_graph.py | 4 +- vitrage/notifier/__init__.py | 7 +- .../notifier/plugins/aodh/aodh_notifier.py | 131 +++++++++--------- vitrage/notifier/plugins/base.py | 5 + vitrage/notifier/service.py | 18 ++- vitrage/opts.py | 6 +- vitrage/tests/unit/notifier/__init__.py | 15 ++ vitrage/tests/unit/notifier/test_notifier.py | 105 ++++++++++++++ 14 files changed, 422 insertions(+), 127 deletions(-) create mode 100644 vitrage/entity_graph/processor/notifier.py create mode 100644 vitrage/tests/unit/notifier/__init__.py create mode 100644 vitrage/tests/unit/notifier/test_notifier.py diff --git a/vitrage/datasources/aodh/driver.py b/vitrage/datasources/aodh/driver.py index e5de21fc6..fa8380f11 100644 --- a/vitrage/datasources/aodh/driver.py +++ b/vitrage/datasources/aodh/driver.py @@ -35,16 +35,12 @@ class AodhDriver(AlarmDriverBase): return alarm[AodhProps.NAME] 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) return [] - # TODO(iafek): enable the code below - - # try: - # aodh_alarms = self.client.alarms.list() - # return [_convert_alarm(alarm) - # for alarm in aodh_alarms] - # except Exception: - # LOG.error("Exception: %s", traceback.print_exc()) - # return [] def _is_erroneous(self, alarm): return alarm and alarm[AodhProps.STATE] != AodhState.OK @@ -56,22 +52,30 @@ class AodhDriver(AlarmDriverBase): def _is_valid(self, alarm): return True - @staticmethod - def _convert_event_alarm(alarm): - converted_alarm = AodhDriver._convert_base_alarm(alarm) - event_type, resource_id = \ - AodhDriver._parse_event_rule(alarm.event_rule) - converted_alarm[AodhProps.EVENT_TYPE] = event_type - converted_alarm[AodhProps.RESOURCE_ID] = resource_id - return converted_alarm + @classmethod + def _convert_event_alarm(cls, alarm): + res = cls._convert_base_alarm(alarm) + res[AodhProps.EVENT_TYPE] = alarm.event_rule[AodhProps.EVENT_TYPE], + res[AodhProps.RESOURCE_ID] = _parse_query(alarm.event_rule, + AodhProps.RESOURCE_ID) + return res - @staticmethod - def _convert_threshold_alarm(alarm): - converted_alarm = AodhDriver._convert_base_alarm(alarm) - converted_alarm[AodhProps.STATE_TIMESTAMP] = alarm.state_timestamp - converted_alarm[AodhProps.RESOURCE_ID] = \ - AodhDriver._parse_threshold_rule(alarm.threshold_rule) - return converted_alarm + @classmethod + def _convert_threshold_alarm(cls, alarm): + res = cls._convert_base_alarm(alarm) + res[AodhProps.STATE_TIMESTAMP] = alarm.state_timestamp + res[AodhProps.RESOURCE_ID] = _parse_query(alarm.threshold_rule, + AodhProps.RESOURCE_ID) + return res + + @classmethod + def _convert_vitrage_alarm(cls, alarm): + res = cls._convert_base_alarm(alarm) + res[AodhProps.VITRAGE_ID] = _parse_query(alarm.event_rule, + AodhProps.VITRAGE_ID) + res[AodhProps.RESOURCE_ID] = _parse_query(alarm.event_rule, + AodhProps.RESOURCE_ID) + return res @staticmethod def _convert_base_alarm(alarm): @@ -90,32 +94,27 @@ class AodhDriver(AlarmDriverBase): AodhProps.TYPE: alarm.type } - @staticmethod - def _parse_event_rule(rule): - event_type = rule[AodhProps.EVENT_TYPE] - resource_id = \ - AodhDriver._parse_resource_id(rule[AodhProps.QUERY]) - return event_type, resource_id - - @staticmethod - def _parse_threshold_rule(rule): - return AodhDriver._parse_resource_id(rule[AodhProps.QUERY]) - - @staticmethod - def _parse_resource_id(query_fields): - for query in query_fields: - field = query['field'] - if field == AodhProps.RESOURCE_ID: - return query['value'] - else: - return None + @classmethod + def _convert_alarm(cls, alarm): + alarm_type = alarm.type + if alarm_type == AodhProps.EVENT and _is_vitrage_alarm(alarm): + return cls._convert_vitrage_alarm(alarm) + elif alarm_type == AodhProps.EVENT: + return cls._convert_event_alarm(alarm) + elif alarm_type == AodhProps.THRESHOLD: + return cls._convert_threshold_alarm(alarm) + else: + LOG.warning('Unsupported Aodh alarm of type %s' % alarm_type) -def _convert_alarm(alarm): - alarm_type = alarm.type - if alarm_type == AodhProps.EVENT: - return AodhDriver._convert_event_alarm(alarm) - elif alarm_type == AodhProps.THRESHOLD: - return AodhDriver._convert_threshold_alarm(alarm) - else: - LOG.info('Unsupported Aodh alarm of type %s' % alarm_type) +def _parse_query(data, key): + query_fields = data.get(AodhProps.QUERY, {}) + for query in query_fields: + field = query['field'] + if field == key: + return query['value'] + return None + + +def _is_vitrage_alarm(alarm): + return _parse_query(alarm.event_rule, AodhProps.VITRAGE_ID) is not None diff --git a/vitrage/datasources/aodh/properties.py b/vitrage/datasources/aodh/properties.py index bb893945b..f1ed8ff68 100644 --- a/vitrage/datasources/aodh/properties.py +++ b/vitrage/datasources/aodh/properties.py @@ -30,6 +30,7 @@ class AodhProperties(object): THRESHOLD = 'threshold' TIMESTAMP = 'timestamp' TYPE = 'type' + VITRAGE_ID = 'vitrage_id' class AodhState(object): diff --git a/vitrage/datasources/aodh/transformer.py b/vitrage/datasources/aodh/transformer.py index 434a592f7..e03de9ead 100644 --- a/vitrage/datasources/aodh/transformer.py +++ b/vitrage/datasources/aodh/transformer.py @@ -36,10 +36,14 @@ class AodhTransformer(AlarmTransformerBase): super(AodhTransformer, self).__init__(transformers) def _create_snapshot_entity_vertex(self, entity_event): - self._create_vertex(entity_event) + if _is_vitrage_alarm(entity_event): + return self._create_merge_alarm_vertex(entity_event) + return self._create_vertex(entity_event) def _create_update_entity_vertex(self, entity_event): - self._create_vertex(entity_event) + if _is_vitrage_alarm(entity_event): + return self._create_merge_alarm_vertex(entity_event) + return self._create_vertex(entity_event) def _create_vertex(self, entity_event): metadata = { @@ -74,6 +78,30 @@ class AodhTransformer(AlarmTransformerBase): update_timestamp=update_timestamp, metadata=metadata) + def _create_merge_alarm_vertex(self, entity_event): + """Handle an alarm that already has a vitrage_id + + This is a deduced alarm created in aodh by vitrage, so it already + exists in the graph. + This function will update the exiting vertex (and not create a new one) + """ + metadata = { + AodhProps.DESCRIPTION: entity_event[AodhProps.DESCRIPTION], + VProps.PROJECT_ID: entity_event[AodhProps.PROJECT_ID], + } + sample_timestamp = entity_event[DSProps.SAMPLE_DATE] + update_timestamp = self._format_update_timestamp( + AodhTransformer._timestamp(entity_event), sample_timestamp) + + return graph_utils.create_vertex( + self._create_entity_key(entity_event), + entity_id=entity_event.get(AodhProps.ALARM_ID), + entity_category=EntityCategory.ALARM, + entity_type='vitrage', + sample_timestamp=sample_timestamp, + update_timestamp=update_timestamp, + metadata=metadata) + def _create_neighbors(self, entity_event): graph_neighbors = entity_event.get(self.QUERY_RESULT, []) result = [] @@ -89,6 +117,9 @@ class AodhTransformer(AlarmTransformerBase): return entity_event[AodhProps.STATE] == self.STATUS_OK def _create_entity_key(self, entity_event): + if _is_vitrage_alarm(entity_event): + return entity_event.get(AodhProps.VITRAGE_ID) + sync_type = entity_event[DSProps.SYNC_TYPE] alarm_name = entity_event[AodhProps.NAME] resource_id = entity_event[AodhProps.RESOURCE_ID] @@ -110,3 +141,7 @@ class AodhTransformer(AlarmTransformerBase): if not affected_resource_id: return None return {VProps.ID: affected_resource_id} + + +def _is_vitrage_alarm(entity_event): + return entity_event.get(AodhProps.VITRAGE_ID) is not None diff --git a/vitrage/entity_graph/__init__.py b/vitrage/entity_graph/__init__.py index b4d1b3af2..900b91bd5 100644 --- a/vitrage/entity_graph/__init__.py +++ b/vitrage/entity_graph/__init__.py @@ -22,4 +22,8 @@ OPTS = [ help='A path for the configuration files of the data sources' ' values' ), + cfg.StrOpt('notifier_topic', + default='vitrage.graph', + help='The topic that vitrage-graph uses for alarm ' + 'notification messages.'), ] diff --git a/vitrage/entity_graph/processor/notifier.py b/vitrage/entity_graph/processor/notifier.py new file mode 100644 index 000000000..9eebdf070 --- /dev/null +++ b/vitrage/entity_graph/processor/notifier.py @@ -0,0 +1,95 @@ +# 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 +import oslo_messaging + +from vitrage.common.constants import EntityCategory +from vitrage.common.constants import NotifierEventTypes +from vitrage.common.constants import VertexProperties as VProps +from vitrage.evaluator.actions import evaluator_event_transformer as evaluator +from vitrage.messaging import get_transport + + +LOG = log.getLogger(__name__) + + +class DeducedAlarmNotifier(object): + """Allows writing to message bus""" + def __init__(self, conf): + self.oslo_notifier = None + try: + topic = conf.entity_graph.notifier_topic + notifier_plugins = conf.notifiers + if not topic or not notifier_plugins: + LOG.info('DeducedAlarmNotifier is disabled') + return + + self.oslo_notifier = oslo_messaging.Notifier( + get_transport(conf), + driver='messagingv2', + publisher_id='vitrage.deduced', + topic=topic) + except Exception: + LOG.info('DeducedAlarmNotifier missing configuration') + + @property + def enabled(self): + return self.oslo_notifier is not None + + def notify_when_applicable(self, before, current, is_vertex): + """Callback subscribed to driver.graph updates + + :param is_vertex: + :param before: The graph element (vertex or edge) prior to the + change that happened. None if the element was just created. + :param current: The graph element (vertex or edge) after the + change that happened. Deleted elements should arrive with the + is_deleted property set to True + """ + notification_type = _get_notification_type(before, current, is_vertex) + if not notification_type: + return + + LOG.debug('DeducedAlarmNotifier : %s', notification_type) + LOG.debug('DeducedAlarmNotifier : %s', current.properties) + + try: + self.oslo_notifier.info({}, notification_type, current.properties) + except Exception as e: + LOG.exception('DeducedAlarmNotifier cannot notify - %s', e) + + +def _get_notification_type(before, current, is_vertex): + if not is_vertex: + return None + if not _is_active_deduced_alarm(before) and \ + _is_active_deduced_alarm(current): + return NotifierEventTypes.ACTIVATE_DEDUCED_ALARM_EVENT + if _is_active_deduced_alarm(before) and \ + not _is_active_deduced_alarm(current): + return NotifierEventTypes.DEACTIVATE_DEDUCED_ALARM_EVENT + + +def _is_active_deduced_alarm(vertex): + if not vertex: + return False + + if not (vertex.get(VProps.CATEGORY) == EntityCategory.ALARM and + vertex.get(VProps.TYPE) == evaluator.VITRAGE_TYPE): + return False + + if vertex.get(VProps.IS_DELETED, False) or \ + vertex.get(VProps.IS_PLACEHOLDER, False): + return False + return True diff --git a/vitrage/entity_graph/processor/processor.py b/vitrage/entity_graph/processor/processor.py index 83e354aa8..03e2aeec6 100644 --- a/vitrage/entity_graph/processor/processor.py +++ b/vitrage/entity_graph/processor/processor.py @@ -20,6 +20,7 @@ from vitrage.common.constants import VertexProperties as VProps from vitrage.datasources.transformer_base import TransformerBase from vitrage.entity_graph.processor import base as processor from vitrage.entity_graph.processor import entity_graph +from vitrage.entity_graph.processor.notifier import DeducedAlarmNotifier from vitrage.entity_graph.states.state_manager import StateManager from vitrage.entity_graph.transformer_manager import TransformerManager from vitrage.graph import Direction @@ -38,6 +39,7 @@ class Processor(processor.ProcessorBase): self.initialization_status = initialization_status self.entity_graph = entity_graph.EntityGraph("Entity Graph") if \ e_graph is None else e_graph + self._notifier = DeducedAlarmNotifier(conf) def process_event(self, event): """Decides which action to run on given event @@ -55,7 +57,7 @@ class Processor(processor.ProcessorBase): self._enrich_event(event) entity = self.transformer_manager.transform(event) self._calculate_aggregated_state(entity.vertex, entity.action) - return self.actions[entity.action](entity.vertex, entity.neighbors) + self.actions[entity.action](entity.vertex, entity.neighbors) def create_entity(self, new_vertex, neighbors): """Adds new vertex to the entity graph @@ -97,8 +99,8 @@ class Processor(processor.ProcessorBase): updated_vertex) self._update_neighbors(updated_vertex, neighbors) else: - LOG.info("Update event arrived on invalid resource: %s", - updated_vertex) + LOG.warning("Update event arrived on invalid resource: %s", + updated_vertex) def delete_entity(self, deleted_vertex, neighbors): """Deletes the vertex from the entity graph @@ -132,8 +134,8 @@ class Processor(processor.ProcessorBase): self.entity_graph.mark_vertex_as_deleted(deleted_vertex) else: - LOG.info("Delete event arrived on invalid resource: %s", - deleted_vertex) + LOG.warning("Delete event arrived on invalid resource: %s", + deleted_vertex) def update_relationship(self, entity_vertex, neighbors): LOG.debug('Update relationship in entity graph:\n%s', neighbors) @@ -159,6 +161,12 @@ class Processor(processor.ProcessorBase): len(self.conf.datasources.types): self.initialization_status.status = \ self.initialization_status.RECEIVED_ALL_END_MESSAGES + self.do_on_initialization_end() + + def do_on_initialization_end(self): + if self._notifier.enabled: + self.entity_graph.subscribe(self._notifier.notify_when_applicable) + LOG.info('Graph notifications subscription added') def _update_neighbors(self, vertex, neighbors): """Updates vertices neighbor connections diff --git a/vitrage/graph/driver/networkx_graph.py b/vitrage/graph/driver/networkx_graph.py index 3d9ea7691..758ef4319 100644 --- a/vitrage/graph/driver/networkx_graph.py +++ b/vitrage/graph/driver/networkx_graph.py @@ -58,7 +58,7 @@ class NXGraph(Graph): self_copy._g = self._g.copy() return self_copy - @Notifier.add_notify + @Notifier.update_notify def add_vertex(self, v): """Add a vertex to the graph @@ -71,7 +71,7 @@ class NXGraph(Graph): properties_copy = copy.copy(v.properties) self._g.add_node(n=v.vertex_id, attr_dict=properties_copy) - @Notifier.add_notify + @Notifier.update_notify def add_edge(self, e): """Add an edge to the graph diff --git a/vitrage/notifier/__init__.py b/vitrage/notifier/__init__.py index 70097d786..7cb40bc77 100644 --- a/vitrage/notifier/__init__.py +++ b/vitrage/notifier/__init__.py @@ -12,4 +12,9 @@ # License for the specific language governing permissions and limitations # under the License. -pass +from oslo_config import cfg + +OPTS = [ + cfg.ListOpt('notifiers', + help='Names of enabled notifiers (example aodh)'), + ] diff --git a/vitrage/notifier/plugins/aodh/aodh_notifier.py b/vitrage/notifier/plugins/aodh/aodh_notifier.py index 2c386a883..4993023ac 100644 --- a/vitrage/notifier/plugins/aodh/aodh_notifier.py +++ b/vitrage/notifier/plugins/aodh/aodh_notifier.py @@ -11,87 +11,92 @@ # 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 random -import string - from oslo_log import log as logging from vitrage import clients from vitrage.common.constants import NotifierEventTypes +from vitrage.common.constants import VertexProperties as VProps +from vitrage.datasources.aodh.properties import AodhState +from vitrage.entity_graph.states.normalized_alarm_severity import \ + NormalizedAlarmSeverity from vitrage.notifier.plugins.base import NotifierBase LOG = logging.getLogger(__name__) -def aodh_alarm_name_generator(name, unique=None, size=6, - chars=string.ascii_uppercase + string.digits): - if unique: - return name.join(['_', unique]) - else: - unique = ''.join(random.choice(chars) for _ in range(size)) - return name.join(['_', unique]) +severity_translation = { + NormalizedAlarmSeverity.CRITICAL: 'critical', + NormalizedAlarmSeverity.SEVERE: 'moderate', + NormalizedAlarmSeverity.WARNING: 'low', +} class AodhNotifier(NotifierBase): + @staticmethod + def get_notifier_name(): + return 'aodh' + def __init__(self, conf): super(AodhNotifier, self).__init__(conf) self.client = clients.ceilometer_client(conf) def process_event(self, data, event_type): - if event_type == NotifierEventTypes.DEACTIVATE_DEDUCED_ALARM_EVENT: - self._deactivate_aodh_alarm(data) - elif event_type == NotifierEventTypes.ACTIVATE_DEDUCED_ALARM_EVENT: - self._activate_aodh_alarm(data) + response = None + if event_type == NotifierEventTypes.ACTIVATE_DEDUCED_ALARM_EVENT: + if not data.get(VProps.ID): + response = self._create_aodh_alarm(data, AodhState.ALARM) + else: + response = self._update_aodh_alarm(data, AodhState.ALARM) + elif event_type == NotifierEventTypes.DEACTIVATE_DEDUCED_ALARM_EVENT: + response = self._update_aodh_alarm(data, AodhState.OK) - # noinspection PyMethodMayBeStatic - def _activate_aodh_alarm(self, data): - LOG.info('### Activate aodh alarm') - # alarm_name = aodh_alarm_name_generator( - # data.get(VProps.NAME), - # data.get('affected_resource_id')) - # query = [dict( - # field='resource_id', - # type='string', - # op='eq', - # value=data.get('affected_resource_id'))] - # severity = data.get(VProps.SEVERITY) - # try: - # alarm = self.client.alarms.create( - # name=alarm_name, - # description='Vitrage deduced alarm', - # query=query, - # severity=severity, - # state='alarm', - # type='event', - # event_rule={"event_type": '*'}) - # LOG.info('Aodh Alarm created: ' + str(alarm)) - # except Exception as e: - # LOG.exception('Failed to create Aodh Alarm, Got Exception: %s',e) - # name - # description - # type' : event or threshold - # threshold_rule - # event_rule - # state': ok, alarm, insufficient data - # severity': moderate, critical, low - # enabled - # alarm_actions - # ok_actions - # insufficient_data_actions - # repeat_actions - # project_id - # user_id - # time_constraints + if response and response.alarm_id: + LOG.info('Aodh Alarm id %s: ', response.alarm_id) + else: + LOG.error('Failed to %s Aodh Alarm \n%s', event_type, str(data)) - # noinspection PyMethodMayBeStatic - def _deactivate_aodh_alarm(self, data): - LOG.info('### Deactivate aodh alarm') - # try: - # alarm = self.client.alarms.update( - # alarm_id=data.get(VProps.ID), - # state='ok') - # LOG.info('Aodh Alarm deactivated ' + str(alarm)) - # except Exception as e: - # LOG.exception('Failed to update Aodh Alarm, Got Exception: %s',e) + def _create_aodh_alarm(self, alarm, state): + alarm_request = _alarm_request(alarm, state) + try: + LOG.info('Aodh Alarm - Activate: ' + str(alarm_request)) + return self.client.alarms.create(**alarm_request) + except Exception as e: + LOG.exception('Failed to activate Aodh Alarm Got Exception: %s', e) + return + + def _update_aodh_alarm(self, alarm, state): + aodh_id = alarm.get(VProps.ID) + try: + LOG.info('Aodh Alarm $%s update state %s', aodh_id, state) + return self.client.alarms.update(alarm_id=aodh_id, state=state) + except Exception as e: + LOG.exception('Failed to update Aodh Alarm Got Exception: %s', e) + return + + +def _alarm_request(data, state): + # TODO(ihefetz) resource id should come from the alarm + affected_resource_id = data.get(VProps.VITRAGE_ID).replace( + 'ALARM:vitrage:deduced_vm_alarm:RESOURCE:nova.instance:', '') + alarm_name = data.get(VProps.NAME) + aodh_alarm_name = '_'.join([alarm_name, affected_resource_id]) + severity = severity_translation.get(data.get(VProps.SEVERITY), 'low') + return dict( + name=aodh_alarm_name, + description=u'Vitrage deduced alarm', + event_rule=dict(query=[ + dict( + field=u'resource_id', + type='', + op=u'eq', + value=affected_resource_id), + dict( + field=u'vitrage_id', + type='', + op=u'eq', + value=data.get(VProps.VITRAGE_ID))]), + severity=severity, + state=state, + type=u'event') diff --git a/vitrage/notifier/plugins/base.py b/vitrage/notifier/plugins/base.py index 7ca51311b..3e031c99b 100644 --- a/vitrage/notifier/plugins/base.py +++ b/vitrage/notifier/plugins/base.py @@ -25,3 +25,8 @@ class NotifierBase(object): @abc.abstractmethod def process_event(self, data, event_type): pass + + @staticmethod + @abc.abstractmethod + def get_notifier_name(): + pass diff --git a/vitrage/notifier/service.py b/vitrage/notifier/service.py index 45fbb192c..e1836413b 100644 --- a/vitrage/notifier/service.py +++ b/vitrage/notifier/service.py @@ -28,9 +28,9 @@ class VitrageNotifierService(os_service.Service): def __init__(self, conf): super(VitrageNotifierService, self).__init__() self.conf = conf - self.notifiers = [AodhNotifier(conf)] + self.notifiers = self.get_notifier_plugins(conf) transport = messaging.get_transport(conf) - target = oslo_messaging.Target(topic='stam') + target = oslo_messaging.Target(topic=conf.entity_graph.notifier_topic) self.listener = messaging.get_notification_listener( transport, [target], [VitrageEventEndpoint(self.notifiers)]) @@ -52,6 +52,20 @@ class VitrageNotifierService(os_service.Service): LOG.info("Vitrage Notifier Service - Stopped!") + @staticmethod + def get_notifier_plugins(conf): + notifiers = [] + conf_notifier_names = conf.notifiers + if not conf_notifier_names: + LOG.info('There are no notifier plugins in configuration') + return [] + for plugin in [AodhNotifier]: + plugin_name = plugin.get_notifier_name() + if plugin_name in conf_notifier_names: + LOG.info('Notifier plugin %s started', plugin_name) + notifiers.append(plugin(conf)) + return notifiers + class VitrageEventEndpoint(object): diff --git a/vitrage/opts.py b/vitrage/opts.py index 30149b6e4..ebf6f0c7a 100644 --- a/vitrage/opts.py +++ b/vitrage/opts.py @@ -23,6 +23,7 @@ import vitrage.datasources import vitrage.entity_graph.consistency import vitrage.evaluator import vitrage.keystone_client +import vitrage.notifier import vitrage.rpc DATASOURCES_PATH = 'vitrage.datasources.' @@ -39,7 +40,10 @@ def list_opts(): ('consistency', vitrage.entity_graph.consistency.OPTS), ('entity_graph', vitrage.entity_graph.OPTS), ('service_credentials', vitrage.keystone_client.OPTS), - ('DEFAULT', itertools.chain(vitrage.clients.OPTS, vitrage.rpc.OPTS)) + ('DEFAULT', itertools.chain( + vitrage.clients.OPTS, + vitrage.rpc.OPTS, + vitrage.notifier.OPTS)) ] diff --git a/vitrage/tests/unit/notifier/__init__.py b/vitrage/tests/unit/notifier/__init__.py new file mode 100644 index 000000000..7a05fb1f4 --- /dev/null +++ b/vitrage/tests/unit/notifier/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2015 - Alcatel-Lucent +# +# 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/notifier/test_notifier.py b/vitrage/tests/unit/notifier/test_notifier.py new file mode 100644 index 000000000..889c62ef6 --- /dev/null +++ b/vitrage/tests/unit/notifier/test_notifier.py @@ -0,0 +1,105 @@ +# Copyright 2016 - Alcatel-Lucent +# +# 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. + +""" +test_vitrage graph +---------------------------------- + +Tests for `vitrage` graph driver +""" + +from vitrage.common.constants import EntityCategory +from vitrage.common.constants import NotifierEventTypes as NType +from vitrage.common.constants import VertexProperties as VProps +from vitrage.entity_graph.processor.notifier import _get_notification_type +from vitrage.evaluator.actions import evaluator_event_transformer as evaluator +from vitrage.graph import Vertex +from vitrage.tests import base + + +resource = Vertex('123', { + VProps.CATEGORY: EntityCategory.RESOURCE, + VProps.TYPE: 'some_resource_type', + VProps.IS_DELETED: False, + VProps.IS_PLACEHOLDER: False, + } +) + +deduced_alarm = Vertex('123', { + VProps.CATEGORY: EntityCategory.ALARM, + VProps.TYPE: evaluator.VITRAGE_TYPE, + VProps.IS_DELETED: False, + VProps.IS_PLACEHOLDER: False, + } +) + + +non_deduced_alarm = Vertex('123', { + VProps.CATEGORY: EntityCategory.ALARM, + VProps.TYPE: 'TEST_ALARM', + VProps.IS_DELETED: False, + VProps.IS_PLACEHOLDER: True, + } +) + +deleted_alarm = Vertex('123', { + VProps.CATEGORY: EntityCategory.ALARM, + VProps.TYPE: evaluator.VITRAGE_TYPE, + VProps.IS_DELETED: True, + VProps.IS_PLACEHOLDER: False, + } +) + +placeholder_alarm = Vertex('123', { + VProps.CATEGORY: EntityCategory.ALARM, + VProps.TYPE: evaluator.VITRAGE_TYPE, + VProps.IS_DELETED: False, + VProps.IS_PLACEHOLDER: True, + } +) + + +class GraphTest(base.BaseTest): + def test_notification_type_new_alarm(self): + ret = _get_notification_type(None, deduced_alarm, True) + self.assertEqual(NType.ACTIVATE_DEDUCED_ALARM_EVENT, ret, + 'new alarm should notify activate') + + ret = _get_notification_type(None, non_deduced_alarm, True) + self.assertIsNone(ret, 'alarm that is not a deduced alarm') + + def test_notification_type_deleted_alarm(self): + ret = _get_notification_type(deduced_alarm, deleted_alarm, True) + self.assertEqual(NType.DEACTIVATE_DEDUCED_ALARM_EVENT, ret, + 'deleted alarm should notify deactivate') + + def test_notification_type_resource_vertex(self): + ret = _get_notification_type(None, resource, True) + self.assertIsNone(ret, 'any non alarm vertex should be ignored') + + def test_notification_type_updated_alarm(self): + ret = _get_notification_type(deduced_alarm, deduced_alarm, True) + self.assertIsNone(ret, 'A not new alarm vertex should be ignored') + + ret = _get_notification_type(deleted_alarm, deduced_alarm, True) + self.assertEqual(NType.ACTIVATE_DEDUCED_ALARM_EVENT, ret, + 'old alarm become not deleted should notify activate') + + ret = _get_notification_type(placeholder_alarm, deduced_alarm, True) + self.assertEqual(NType.ACTIVATE_DEDUCED_ALARM_EVENT, ret, + 'placeholder become active should notify activate') + + def test_notification_type_placeholder_alarm(self): + ret = _get_notification_type(None, placeholder_alarm, True) + self.assertIsNone(ret, 'A not new alarm vertex should be ignored')