# 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. 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.ceilometer import CEILOMETER_DATASOURCE from vitrage.datasources.ceilometer.properties \ import CeilometerEventType as CeilEventType from vitrage.datasources.ceilometer.properties \ import CeilometerProperties as CeilProps from vitrage.datasources.ceilometer.properties \ import CeilometerState as CeilState from vitrage import os_clients from vitrage.utils import datetime as datetime_utils LOG = log.getLogger(__name__) class CeilometerDriver(AlarmDriverBase): def __init__(self): super(CeilometerDriver, self).__init__() self._client = None self._init_aodh_event_actions() self._cache_all_alarms() @property def client(self): if not self._client: self._client = os_clients.ceilometer_client() return self._client def _vitrage_type(self): return CEILOMETER_DATASOURCE def _alarm_key(self, alarm): return alarm[CeilProps.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 if alarm is not None] except Exception: LOG.exception("Failed to get all alarms.") return [] def _is_erroneous(self, alarm): return alarm and alarm[CeilProps.STATE] == CeilState.ALARM def _status_changed(self, alarm1, alarm2): return alarm1 and alarm2 and \ not alarm1[CeilProps.STATE] == alarm2[CeilProps.STATE] def _is_valid(self, alarm): return True @classmethod def _convert_event_alarm(cls, alarm): res = cls._convert_base_alarm(alarm) res[CeilProps.EVENT_TYPE] = alarm.event_rule[CeilProps.EVENT_TYPE], res[CeilProps.RESOURCE_ID] = _parse_query(alarm.event_rule, CeilProps.EVENT_RESOURCE_ID) return res @classmethod def _convert_threshold_alarm(cls, alarm): res = cls._convert_base_alarm(alarm) res[CeilProps.STATE_TIMESTAMP] = alarm.state_timestamp res[CeilProps.RESOURCE_ID] = _parse_query(alarm.threshold_rule, CeilProps.RESOURCE_ID) return res @classmethod def _convert_gnocchi_resources_threshold(cls, alarm): res = cls._convert_base_alarm_gnocchi(alarm) if type(alarm) is not dict: alarm = alarm.to_dict() res[CeilProps.STATE_TIMESTAMP] = \ alarm.get(CeilProps.STATE_TIMESTAMP) res[CeilProps.RESOURCE_ID] = \ alarm.get(CeilProps.GNOCCHI_RESOURCES_THRESHOLD_RULE, {}).get(CeilProps.RESOURCE_ID) else: res[CeilProps.STATE_TIMESTAMP] = \ alarm.get(CeilProps.DETAIL, {}).get(CeilProps.STATE_TIMESTAMP) res[CeilProps.RESOURCE_ID] = \ alarm.get(CeilProps.DETAIL, {}).get(CeilProps.RULE, {}).get(CeilProps.RESOURCE_ID) return res @classmethod def _convert_vitrage_alarm(cls, alarm): res = cls._convert_base_alarm(alarm) res[CeilProps.VITRAGE_ID] = _parse_query(alarm.event_rule, CeilProps.VITRAGE_ID) res[CeilProps.RESOURCE_ID] = _parse_query(alarm.event_rule, CeilProps.RESOURCE_ID) return res @staticmethod def _convert_base_dict_alarm_gnocchi(alarm): detail = alarm.get(CeilProps.DETAIL) return { CeilProps.SEVERITY: alarm.get(CeilProps.SEVERITY), CeilProps.PROJECT_ID: alarm.get(CeilProps.PROJECT_ID), CeilProps.TIMESTAMP: alarm.get(CeilProps.TIMESTAMP), CeilProps.TYPE: alarm.get(CeilProps.TYPE), CeilProps.ALARM_ID: alarm.get(CeilProps.ALARM_ID), CeilProps.DESCRIPTION: detail.get(CeilProps.DESCRIPTION), CeilProps.ENABLED: detail.get(CeilProps.ENABLED), CeilProps.NAME: detail.get(CeilProps.NAME), CeilProps.REPEAT_ACTIONS: detail.get(CeilProps.REPEAT_ACTIONS), CeilProps.STATE: detail.get(CeilProps.STATE) } @staticmethod def _convert_base_non_dict_alarm_gnocchi(alarm): alarm = alarm.to_dict() return { CeilProps.SEVERITY: alarm.get(CeilProps.SEVERITY), CeilProps.DESCRIPTION: alarm.get(CeilProps.DESCRIPTION), CeilProps.ENABLED: alarm.get(CeilProps.ENABLED), CeilProps.ALARM_ID: alarm.get(CeilProps.ALARM_ID), CeilProps.NAME: alarm.get(CeilProps.NAME), CeilProps.PROJECT_ID: alarm.get(CeilProps.PROJECT_ID), CeilProps.REPEAT_ACTIONS: alarm.get(CeilProps.REPEAT_ACTIONS), CeilProps.STATE: alarm.get(CeilProps.STATE), CeilProps.TIMESTAMP: alarm.get(CeilProps.TIMESTAMP), CeilProps.TYPE: alarm.get(CeilProps.TYPE) } @classmethod def _convert_base_alarm_gnocchi(cls, alarm): """distinguish between alarm received by notification (type dict) to alarm received by _get_alarms() (type alarm). """ if type(alarm) is dict: return cls._convert_base_dict_alarm_gnocchi(alarm) return cls._convert_base_non_dict_alarm_gnocchi(alarm) @staticmethod def _convert_base_alarm(alarm): return { CeilProps.SEVERITY: alarm.severity, CeilProps.DESCRIPTION: alarm.description, CeilProps.ENABLED: alarm.enabled, CeilProps.ALARM_ID: alarm.alarm_id, CeilProps.NAME: alarm.name, CeilProps.PROJECT_ID: alarm.project_id, CeilProps.REPEAT_ACTIONS: alarm.repeat_actions, CeilProps.STATE: alarm.state, CeilProps.TIMESTAMP: alarm.timestamp, CeilProps.TYPE: alarm.type } @classmethod def _convert_alarm(cls, alarm): alarm_type = alarm.type if alarm_type == CeilProps.EVENT and \ _is_vitrage_alarm(alarm.event_rule): return cls._convert_vitrage_alarm(alarm) elif alarm_type == CeilProps.EVENT: return cls._convert_event_alarm(alarm) elif alarm_type == CeilProps.THRESHOLD: return cls._convert_threshold_alarm(alarm) elif alarm_type == CeilProps.GNOCCHI_RESOURCES_THRESHOLD: return cls._convert_gnocchi_resources_threshold(alarm) else: LOG.warning('Unsupported Ceilometer alarm type %s' % alarm_type) @staticmethod def get_event_types(): # Add event_types to receive notifications about return [CeilEventType.CREATION, CeilEventType.STATE_TRANSITION, CeilEventType.RULE_CHANGE, CeilEventType.DELETION] def enrich_event(self, event, event_type): if event_type in self.actions: entity = self.actions[event_type](event) else: LOG.warning('Unsupported Ceilometer 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 CeilometerDriver.make_pickleable( [entity], CEILOMETER_DATASOURCE, DatasourceAction.UPDATE)[0] def _init_aodh_event_actions(self): self.actions = { CeilEventType.CREATION: self._convert_alarm_creation_event, CeilEventType.RULE_CHANGE: self._convert_alarm_rule_change_event, CeilEventType.STATE_TRANSITION: self._convert_alarm_state_transition_event, CeilEventType.DELETION: self._convert_alarm_deletion_event } @classmethod def _convert_base_event(cls, event): return { CeilProps.PROJECT_ID: event[CeilProps.PROJECT_ID], CeilProps.ALARM_ID: event[CeilProps.ALARM_ID], CeilProps.SEVERITY: event[CeilProps.SEVERITY], CeilProps.TIMESTAMP: event[CeilProps.TIMESTAMP], CeilProps.USER_ID: event[CeilProps.USER_ID] } @classmethod def _convert_vitrage_alarm_rule(cls, rule): return { CeilProps.VITRAGE_ID: _parse_query(rule, CeilProps.VITRAGE_ID), CeilProps.RESOURCE_ID: _parse_query(rule, CeilProps.RESOURCE_ID) } @classmethod def _convert_threshold_alarm_rule(cls, rule): return { CeilProps.RESOURCE_ID: _parse_query(rule, CeilProps.RESOURCE_ID), } @classmethod def _convert_gnocchi_resources_threshold_alarm_rule(cls, rule): return { CeilProps.RESOURCE_ID: _parse_query(rule, CeilProps.RESOURCE_ID), } @classmethod def _convert_event_alarm_rule(cls, rule): return { CeilProps.EVENT_TYPE: rule[CeilProps.EVENT_TYPE], CeilProps.RESOURCE_ID: _parse_query(rule, CeilProps.EVENT_RESOURCE_ID) } @classmethod def _convert_detail_event(cls, event): alarm_info = event[CeilProps.DETAIL] alarm_rule = alarm_info[CeilProps.RULE] entity_detail = { CeilProps.DESCRIPTION: alarm_info[CeilProps.DESCRIPTION], CeilProps.ENABLED: alarm_info[CeilProps.ENABLED], CeilProps.NAME: alarm_info[CeilProps.NAME], CeilProps.STATE: alarm_info[CeilProps.STATE], CeilProps.REPEAT_ACTIONS: alarm_info[CeilProps.REPEAT_ACTIONS], CeilProps.TYPE: alarm_info[CeilProps.TYPE], CeilProps.STATE_TIMESTAMP: alarm_info[CeilProps.STATE_TIMESTAMP], CeilProps.STATE_REASON: alarm_info[CeilProps.STATE_REASON] } if _is_vitrage_alarm(alarm_rule): entity_detail.update(cls._convert_vitrage_alarm_rule(alarm_rule)) elif entity_detail[CeilProps.TYPE] == CeilProps.EVENT: entity_detail.update(cls._convert_event_alarm_rule(alarm_rule)) elif entity_detail[CeilProps.TYPE] == CeilProps.THRESHOLD: entity_detail.update( cls._convert_threshold_alarm_rule(alarm_rule)) elif entity_detail[CeilProps.TYPE] == \ CeilProps.GNOCCHI_RESOURCES_THRESHOLD: entity_detail.update( cls._convert_gnocchi_resources_threshold_alarm_rule( alarm_rule)) return entity_detail @classmethod def _parse_changed_rule(cls, change_rule): entity = {} if CeilProps.EVENT_TYPE in change_rule: entity[CeilProps.EVENT_TYPE] = change_rule[CeilProps.EVENT_TYPE] if 'query' in change_rule: event_resource_id = \ _parse_query(change_rule, CeilProps.EVENT_RESOURCE_ID) resource_id = \ _parse_query(change_rule, CeilProps.RESOURCE_ID) if event_resource_id or resource_id: entity[CeilProps.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"}} """ old_alarm = self._old_alarm(event) entity = old_alarm.copy() changed_rule = event[CeilProps.DETAIL] for (changed_type, changed_info) in changed_rule.items(): # handle changed rule which may effect the neighbor if changed_type == CeilProps.RULE: entity.update(self._parse_changed_rule( changed_rule[changed_type])) # handle other changed alarm properties elif changed_type in vars(CeilProps).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): old_alarm = self._old_alarm(event) entity = old_alarm.copy() try: entity[CeilProps.STATE] = event[CeilProps.DETAIL][CeilProps.STATE] except Exception: LOG.exception("Failed to Convert alarm state transition event.") 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(CeilProps.QUERY, {}) for query in query_fields: field = query['field'] if field == key: return query['value'] return None def _is_vitrage_alarm(rule): return _parse_query(rule, CeilProps.VITRAGE_ID) is not None