diff --git a/vitrage/clients.py b/vitrage/clients.py index 49d4de25d..a8ef60e5e 100644 --- a/vitrage/clients.py +++ b/vitrage/clients.py @@ -26,7 +26,7 @@ LOG = log.getLogger(__name__) OPTS = [ cfg.StrOpt('aodh_version', default='2', help='Aodh version'), - cfg.FloatOpt('nova_version', default='2.0', help='Nova version'), + cfg.FloatOpt('nova_version', default='2.11', help='Nova version'), cfg.StrOpt('cinder_version', default='1', help='Cinder version'), ] diff --git a/vitrage/entity_graph/__init__.py b/vitrage/entity_graph/__init__.py index 900b91bd5..3d0635040 100644 --- a/vitrage/entity_graph/__init__.py +++ b/vitrage/entity_graph/__init__.py @@ -24,6 +24,6 @@ OPTS = [ ), cfg.StrOpt('notifier_topic', default='vitrage.graph', - help='The topic that vitrage-graph uses for alarm ' + help='The topic that vitrage-graph uses for graph ' 'notification messages.'), ] diff --git a/vitrage/entity_graph/processor/notifier.py b/vitrage/entity_graph/processor/notifier.py index 3e86cfa2b..bb3db4920 100644 --- a/vitrage/entity_graph/processor/notifier.py +++ b/vitrage/entity_graph/processor/notifier.py @@ -24,7 +24,7 @@ from vitrage.messaging import get_transport LOG = log.getLogger(__name__) -class DeducedAlarmNotifier(object): +class GraphNotifier(object): """Allows writing to message bus""" def __init__(self, conf): self.oslo_notifier = None @@ -32,16 +32,16 @@ class DeducedAlarmNotifier(object): topic = conf.entity_graph.notifier_topic notifier_plugins = conf.notifiers if not topic or not notifier_plugins: - LOG.info('DeducedAlarmNotifier is disabled') + LOG.info('Graph Notifier is disabled') return self.oslo_notifier = oslo_messaging.Notifier( get_transport(conf), driver='messagingv2', - publisher_id='vitrage.deduced', + publisher_id='vitrage.graph', topic=topic) except Exception as e: - LOG.info('DeducedAlarmNotifier missing configuration %s' % str(e)) + LOG.info('Graph Notifier - missing configuration %s' % str(e)) @property def enabled(self): @@ -57,38 +57,67 @@ class DeducedAlarmNotifier(object): 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: + notification_types = _get_notification_type(before, current, is_vertex) + if not notification_types: return - LOG.debug('DeducedAlarmNotifier : %s', notification_type) - LOG.debug('DeducedAlarmNotifier : %s', current.properties) + LOG.info('notification_types : %s', str(notification_types)) + LOG.info('notification properties : %s', current.properties) - try: - self.oslo_notifier.info({}, notification_type, current.properties) - except Exception as e: - LOG.exception('DeducedAlarmNotifier cannot notify - %s', e) + for notification_type in notification_types: + try: + self.oslo_notifier.info( + {}, + notification_type, + current.properties) + except Exception as e: + LOG.exception('Cannot notify - %s - %s', notification_type, 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 notification_type(is_active, + activate_event_type, + deactivate_event_type): + if not is_active(before): + if is_active(current): + return activate_event_type + else: + if not is_active(current): + return deactivate_event_type + + notification_types = [ + notification_type(_is_active_deduced_alarm, + NotifierEventTypes.ACTIVATE_DEDUCED_ALARM_EVENT, + NotifierEventTypes.DEACTIVATE_DEDUCED_ALARM_EVENT), + notification_type(_is_marked_down, + NotifierEventTypes.ACTIVATE_MARK_DOWN_EVENT, + NotifierEventTypes.DEACTIVATE_MARK_DOWN_EVENT), + ] + return list(filter(None, notification_types)) def _is_active_deduced_alarm(vertex): if not vertex: return False + if vertex.get(VProps.CATEGORY) == EntityCategory.ALARM and \ + vertex.get(VProps.TYPE) == evaluator.VITRAGE_TYPE: + return _is_relevant_vertex(vertex) + return False - if not (vertex.get(VProps.CATEGORY) == EntityCategory.ALARM and - vertex.get(VProps.TYPE) == evaluator.VITRAGE_TYPE): + +def _is_marked_down(vertex): + if not vertex: return False + if vertex.get(VProps.CATEGORY) == EntityCategory.RESOURCE and \ + vertex.get(VProps.IS_MARKED_DOWN) is True: + return _is_relevant_vertex(vertex) + return False + +def _is_relevant_vertex(vertex): if vertex.get(VProps.IS_DELETED, False) or \ vertex.get(VProps.IS_PLACEHOLDER, False): return False diff --git a/vitrage/entity_graph/processor/processor.py b/vitrage/entity_graph/processor/processor.py index c6ff6b981..b8689dc1a 100644 --- a/vitrage/entity_graph/processor/processor.py +++ b/vitrage/entity_graph/processor/processor.py @@ -22,7 +22,7 @@ from vitrage.entity_graph.mappings.datasource_info_mapper import \ DatasourceInfoMapper 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.processor.notifier import GraphNotifier from vitrage.entity_graph.processor import processor_utils as PUtils from vitrage.entity_graph.transformer_manager import TransformerManager from vitrage.graph import Direction @@ -41,7 +41,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) + self._notifier = GraphNotifier(conf) def process_event(self, event): """Decides which action to run on given event diff --git a/vitrage/notifier/__init__.py b/vitrage/notifier/__init__.py index 94d46871f..a464c2ff1 100644 --- a/vitrage/notifier/__init__.py +++ b/vitrage/notifier/__init__.py @@ -16,5 +16,5 @@ from oslo_config import cfg OPTS = [ cfg.ListOpt('notifiers', - help='Names of enabled notifiers (example aodh)'), + help='Names of enabled notifiers (example aodh, nova)'), ] diff --git a/vitrage/notifier/plugins/nova/__init__.py b/vitrage/notifier/plugins/nova/__init__.py new file mode 100644 index 000000000..70097d786 --- /dev/null +++ b/vitrage/notifier/plugins/nova/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2016 - Nokia +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +pass diff --git a/vitrage/notifier/plugins/nova/nova_notifier.py b/vitrage/notifier/plugins/nova/nova_notifier.py new file mode 100644 index 000000000..a8684f7f8 --- /dev/null +++ b/vitrage/notifier/plugins/nova/nova_notifier.py @@ -0,0 +1,51 @@ +# 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 as logging + +from vitrage import clients +from vitrage.common.constants import NotifierEventTypes +from vitrage.common.constants import VertexProperties as VProps +from vitrage.datasources import NOVA_HOST_DATASOURCE +from vitrage.notifier.plugins.base import NotifierBase + +LOG = logging.getLogger(__name__) + + +class NovaNotifier(NotifierBase): + + @staticmethod + def get_notifier_name(): + return 'nova' + + def __init__(self, conf): + super(NovaNotifier, self).__init__(conf) + self.client = clients.nova_client(conf) + + def process_event(self, data, event_type): + if data and data.get(VProps.TYPE) is NOVA_HOST_DATASOURCE: + if event_type == NotifierEventTypes.ACTIVATE_MARK_DOWN_EVENT: + self._mark_host_down(data.get(VProps.ID), True) + elif event_type == NotifierEventTypes.DEACTIVATE_MARK_DOWN_EVENT: + self._mark_host_down(data.get(VProps.ID), False) + + def _mark_host_down(self, host_id, is_down): + try: + LOG.info('Nova services.force_down - host id: %s, is_down: %s', + str(host_id), str(is_down)) + response = self.client.services.force_down( + host_id, 'nova-compute', is_down) + LOG.info('RESPONSE %s', str(response)) + except Exception as e: + LOG.exception('Failed to services.force_down - %s', e) + return diff --git a/vitrage/notifier/service.py b/vitrage/notifier/service.py index e1836413b..109b773fb 100644 --- a/vitrage/notifier/service.py +++ b/vitrage/notifier/service.py @@ -18,6 +18,7 @@ from oslo_service import service as os_service from vitrage import messaging from vitrage.notifier.plugins.aodh.aodh_notifier import AodhNotifier +from vitrage.notifier.plugins.nova.nova_notifier import NovaNotifier LOG = log.getLogger(__name__) @@ -59,7 +60,7 @@ class VitrageNotifierService(os_service.Service): if not conf_notifier_names: LOG.info('There are no notifier plugins in configuration') return [] - for plugin in [AodhNotifier]: + for plugin in [AodhNotifier, NovaNotifier]: plugin_name = plugin.get_notifier_name() if plugin_name in conf_notifier_names: LOG.info('Notifier plugin %s started', plugin_name) diff --git a/vitrage/tests/unit/notifier/test_notifier.py b/vitrage/tests/unit/notifier/test_notifier.py index 76796a848..98ac2befb 100644 --- a/vitrage/tests/unit/notifier/test_notifier.py +++ b/vitrage/tests/unit/notifier/test_notifier.py @@ -18,6 +18,7 @@ test_vitrage graph Tests for `vitrage` graph driver """ +import copy from vitrage.common.constants import EntityCategory from vitrage.common.constants import NotifierEventTypes as NType @@ -33,16 +34,14 @@ resource = Vertex('123', { 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', { @@ -50,56 +49,146 @@ non_deduced_alarm = Vertex('123', { 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, -} -) + }) + + +host = Vertex('123', { + VProps.CATEGORY: EntityCategory.RESOURCE, + VProps.TYPE: 'nova.host', + VProps.IS_DELETED: False, + VProps.IS_PLACEHOLDER: False, + }) + +forced_down_host = Vertex('123', { + VProps.CATEGORY: EntityCategory.RESOURCE, + VProps.TYPE: 'nova.host', + VProps.IS_DELETED: False, + VProps.IS_PLACEHOLDER: False, + VProps.IS_MARKED_DOWN: True, + }) class GraphTest(base.BaseTest): + def get_first(self, lst): + self.assertIsNotNone(lst) + return lst[0] if len(lst) > 0 else None + def test_notification_type_new_alarm(self): ret = _get_notification_type(None, deduced_alarm, True) - self.assertEqual(NType.ACTIVATE_DEDUCED_ALARM_EVENT, ret, + self.assertEqual(NType.ACTIVATE_DEDUCED_ALARM_EVENT, + self.get_first(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') + self.assertIsNone(self.get_first(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, + self.assertEqual(NType.DEACTIVATE_DEDUCED_ALARM_EVENT, + self.get_first(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') + self.assertIsNone(self.get_first(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') + self.assertIsNone(self.get_first(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, + self.assertEqual(NType.ACTIVATE_DEDUCED_ALARM_EVENT, + self.get_first(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, + self.assertEqual(NType.ACTIVATE_DEDUCED_ALARM_EVENT, + self.get_first(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') + self.assertIsNone(self.get_first(ret), + 'A not new alarm vertex should be ignored') + + def test_notification_type_new_host(self): + ret = _get_notification_type(None, forced_down_host, True) + self.assertEqual(NType.ACTIVATE_MARK_DOWN_EVENT, + self.get_first(ret), + 'new host with forced_down should notify activate') + + ret = _get_notification_type(None, host, True) + self.assertIsNone(self.get_first(ret), 'host without forced_down') + + def test_notification_type_deleted_host(self): + deleted_host = copy.deepcopy(forced_down_host) + deleted_host[VProps.IS_DELETED] = True + ret = _get_notification_type(forced_down_host, deleted_host, True) + self.assertEqual( + NType.DEACTIVATE_MARK_DOWN_EVENT, + self.get_first(ret), + 'deleted host with forced_down should notify deactivate') + + deleted_host = copy.deepcopy(host) + deleted_host[VProps.IS_DELETED] = True + ret = _get_notification_type(forced_down_host, deleted_host, True) + self.assertEqual( + NType.DEACTIVATE_MARK_DOWN_EVENT, + self.get_first(ret), + 'deleted host with forced_down should notify deactivate') + + deleted_host = copy.deepcopy(host) + deleted_host[VProps.IS_DELETED] = True + ret = _get_notification_type(host, deleted_host, True) + self.assertIsNone( + self.get_first(ret), + 'deleted host without forced_down should not notify') + + def test_notification_type_updated_host(self): + ret = _get_notification_type(forced_down_host, forced_down_host, True) + self.assertIsNone(self.get_first(ret), + 'A not new host should be ignored') + + deleted_host = copy.deepcopy(forced_down_host) + deleted_host[VProps.IS_DELETED] = True + ret = _get_notification_type(deleted_host, forced_down_host, True) + self.assertEqual(NType.ACTIVATE_MARK_DOWN_EVENT, + self.get_first(ret), + 'old host become not deleted should notify activate') + + deleted_host = copy.deepcopy(forced_down_host) + deleted_host[VProps.IS_DELETED] = True + ret = _get_notification_type(deleted_host, host, True) + self.assertIsNone(self.get_first(ret), + 'old host become not deleted should not notify') + + placeholder_host = copy.deepcopy(forced_down_host) + placeholder_host[VProps.IS_PLACEHOLDER] = True + ret = _get_notification_type(placeholder_host, forced_down_host, True) + self.assertEqual(NType.ACTIVATE_MARK_DOWN_EVENT, + self.get_first(ret), + 'placeholder become active should notify activate') + + def test_notification_type_placeholder_host(self): + placeholder_host = copy.deepcopy(forced_down_host) + placeholder_host[VProps.IS_PLACEHOLDER] = True + ret = _get_notification_type(None, placeholder_host, True) + self.assertIsNone(self.get_first(ret), + 'A not new host vertex should be ignored')