vitrage/vitrage/datasources/ceilometer/driver.py

388 lines
15 KiB
Python

# 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
@staticmethod
def should_delete_outdated_entities():
return True
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