fault/fm-rest-api/fm/fm/api/controllers/v1/alarm.py

449 lines
16 KiB
Python

#
# Copyright (c) 2018-2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import datetime
import json
import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from oslo_utils._i18n import _
from oslo_log import log
from fm_api import fm_api
from fm.api.controllers.v1 import base
from fm.api.controllers.v1 import collection
from fm.api.controllers.v1 import link
from fm.api.controllers.v1 import types
from fm.api.controllers.v1 import utils as api_utils
from fm.common import exceptions
from fm.common import constants
from fm import objects
from fm.api.controllers.v1.query import Query
from fm.api.controllers.v1.sysinv import cgtsclient
from fm_api import constants as fm_constants
LOG = log.getLogger(__name__)
class AlarmPatchType(types.JsonPatchType):
pass
class Alarm(base.APIBase):
"""API representation of an alarm.
This class enforces type checking and value constraints, and converts
between the internal object model and the API representation of
an alarm.
"""
uuid = types.uuid
"The UUID of the alarm"
alarm_id = wsme.wsattr(wtypes.text, mandatory=True)
"structured id for the alarm; AREA_ID ID; 300-001"
alarm_state = wsme.wsattr(wtypes.text, mandatory=True)
"The state of the alarm"
entity_type_id = wtypes.text
"The type of the object raising alarm"
entity_instance_id = wsme.wsattr(wtypes.text, mandatory=True)
"The original instance information of the object raising alarm"
timestamp = datetime.datetime
"The time in UTC at which the alarm state is last updated"
severity = wsme.wsattr(wtypes.text, mandatory=True)
"The severity of the alarm"
reason_text = wtypes.text
"The reason why the alarm is raised"
alarm_type = wsme.wsattr(wtypes.text, mandatory=True)
"The type of the alarm"
probable_cause = wsme.wsattr(wtypes.text, mandatory=True)
"The probable cause of the alarm"
proposed_repair_action = wtypes.text
"The action to clear the alarm"
service_affecting = bool
"Whether the alarm affects the service"
suppression = bool
"'allowed' or 'not-allowed'"
suppression_status = wtypes.text
"'suppressed' or 'unsuppressed'"
mgmt_affecting = wtypes.text
"Whether the alarm prevents software management actions"
degrade_affecting = wtypes.text
"Whether the alarm prevents filesystem resize actions"
links = [link.Link]
"A list containing a self link and associated alarm links"
def __init__(self, **kwargs):
self.fields = list(objects.alarm.fields.keys())
for k in self.fields:
setattr(self, k, kwargs.get(k))
@classmethod
def convert_with_links(cls, rpc_ialarm, expand=True):
if isinstance(rpc_ialarm, tuple):
alarms = rpc_ialarm[0]
suppress_status = rpc_ialarm[constants.DB_SUPPRESS_STATUS]
mgmt_affecting = rpc_ialarm[constants.DB_MGMT_AFFECTING]
degrade_affecting = rpc_ialarm[constants.DB_DEGRADE_AFFECTING]
else:
alarms = rpc_ialarm
suppress_status = rpc_ialarm.suppression_status
mgmt_affecting = rpc_ialarm.mgmt_affecting
degrade_affecting = rpc_ialarm.degrade_affecting
alarms['service_affecting'] = alarms['service_affecting']
alarms['suppression'] = alarms['suppression']
alm = Alarm(**alarms.as_dict())
if not expand:
alm.unset_fields_except(['uuid', 'alarm_id', 'entity_instance_id',
'severity', 'timestamp', 'reason_text',
'mgmt_affecting ', 'degrade_affecting'])
alm.entity_instance_id = \
api_utils.make_display_id(alm.entity_instance_id, replace=False)
alm.suppression_status = str(suppress_status)
alm.mgmt_affecting = str(
not fm_api.FaultAPIs.alarm_allowed(alm.severity, mgmt_affecting))
alm.degrade_affecting = str(
not fm_api.FaultAPIs.alarm_allowed(alm.severity,
degrade_affecting))
return alm
class AlarmCollection(collection.Collection):
"""API representation of a collection of alarm."""
alarms = [Alarm]
"A list containing alarm objects"
def __init__(self, **kwargs):
self._type = 'alarms'
@classmethod
def convert_with_links(cls, ialm, limit, url=None,
expand=False, **kwargs):
# filter masked alarms
ialms = []
for a in ialm:
if isinstance(a, tuple):
ialm_instance = a[0]
else:
ialm_instance = a
if str(ialm_instance['masked']) != 'True':
ialms.append(a)
collection = AlarmCollection()
collection.alarms = [Alarm.convert_with_links(ch, expand)
for ch in ialms]
# url = url or None
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
LOCK_NAME = 'AlarmController'
class AlarmSummary(base.APIBase):
"""API representation of an alarm summary object."""
critical = wsme.wsattr(int, mandatory=True)
"The count of critical alarms"
major = wsme.wsattr(int, mandatory=True)
"The count of major alarms"
minor = wsme.wsattr(int, mandatory=True)
"The count of minor alarms"
warnings = wsme.wsattr(int, mandatory=True)
"The count of warnings"
status = wsme.wsattr(wtypes.text, mandatory=True)
"The status of the system"
system_uuid = wsme.wsattr(types.uuid, mandatory=True)
"The UUID of the system (for distributed cloud use)"
@classmethod
def convert_with_links(cls, ialm_sum, uuid):
summary = AlarmSummary()
summary.critical = ialm_sum[fm_constants.FM_ALARM_SEVERITY_CRITICAL]
summary.major = ialm_sum[fm_constants.FM_ALARM_SEVERITY_MAJOR]
summary.minor = ialm_sum[fm_constants.FM_ALARM_SEVERITY_MINOR]
summary.warnings = ialm_sum[fm_constants.FM_ALARM_SEVERITY_WARNING]
summary.status = ialm_sum['status']
summary.system_uuid = uuid
return summary
class AlarmController(rest.RestController):
"""REST controller for alarm."""
_custom_actions = {
'detail': ['GET'],
'summary': ['GET'],
}
def _get_alarm_summary(self, include_suppress):
kwargs = {}
kwargs["include_suppress"] = include_suppress
ialm = pecan.request.dbapi.alarm_get_all(**kwargs)
ialm_counts = {fm_constants.FM_ALARM_SEVERITY_CRITICAL: 0,
fm_constants.FM_ALARM_SEVERITY_MAJOR: 0,
fm_constants.FM_ALARM_SEVERITY_MINOR: 0,
fm_constants.FM_ALARM_SEVERITY_WARNING: 0}
# filter masked alarms and sum by severity
for a in ialm:
ialm_instance = a[0]
if str(ialm_instance['masked']) != 'True':
if ialm_instance['severity'] in ialm_counts:
ialm_counts[ialm_instance['severity']] += 1
# Generate the status
status = fm_constants.FM_ALARM_OK_STATUS
if (ialm_counts[fm_constants.FM_ALARM_SEVERITY_MAJOR] > 0) or \
(ialm_counts[fm_constants.FM_ALARM_SEVERITY_MINOR] > 0):
status = fm_constants.FM_ALARM_DEGRADED_STATUS
if ialm_counts[fm_constants.FM_ALARM_SEVERITY_CRITICAL] > 0:
status = fm_constants.FM_ALARM_CRITICAL_STATUS
ialm_counts['status'] = status
system = cgtsclient(pecan.request.context).isystem.list()[0]
uuid = system.uuid
return AlarmSummary.convert_with_links(ialm_counts, uuid)
def _get_alarm_collection(self, marker, limit, sort_key, sort_dir,
expand=False, resource_url=None,
q=None, include_suppress=False):
limit = api_utils.validate_limit(limit)
sort_dir = api_utils.validate_sort_dir(sort_dir)
if isinstance(sort_key, str) and ',' in sort_key:
sort_key = sort_key.split(',')
kwargs = {}
if q is not None:
for i in q:
if i.op == 'eq':
kwargs[i.field] = i.value
kwargs["include_suppress"] = include_suppress
if marker:
marker_obj = objects.alarm.get_by_uuid(pecan.request.context,
marker)
ialm = pecan.request.dbapi.alarm_get_list(
limit, marker_obj,
sort_key=sort_key,
sort_dir=sort_dir,
include_suppress=include_suppress)
else:
kwargs['limit'] = limit
ialm = pecan.request.dbapi.alarm_get_all(**kwargs)
return AlarmCollection.convert_with_links(ialm, limit,
url=resource_url,
expand=expand,
sort_key=sort_key,
sort_dir=sort_dir)
def _get_event_log_data(self, alarm_dict):
""" Retrive a dictionary to create an event_log object
:param alarm_dict: Dictionary obtained from an alarm object.
"""
event_log_dict = {}
for key in list(alarm_dict.keys()):
if key == 'alarm_id':
event_log_dict['event_log_id'] = alarm_dict[key]
elif key == 'alarm_state':
event_log_dict['state'] = alarm_dict[key]
elif key == 'alarm_type':
event_log_dict['event_log_type'] = alarm_dict[key]
elif (
key == 'inhibit_alarms' or key == 'inhibit_alarms' or
key == 'updated_at' or key == 'updated_at' or key == 'masked'
):
continue
else:
event_log_dict[key] = alarm_dict[key]
return event_log_dict
@wsme_pecan.wsexpose(AlarmCollection, [Query],
types.uuid, int, wtypes.text, wtypes.text, bool, bool)
def get_all(self, q=[], marker=None, limit=None, sort_key='id',
sort_dir='asc', include_suppress=False, expand=False):
"""Retrieve a list of alarm.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
:param include_suppress: filter on suppressed alarms. Default: False
:param expand: filter for getting all the data of the alarm.
Default: False
"""
return self._get_alarm_collection(marker, limit, sort_key,
sort_dir, expand=expand, q=q,
include_suppress=include_suppress)
@wsme_pecan.wsexpose(AlarmCollection, types.uuid, int,
wtypes.text, wtypes.text)
def detail(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
"""Retrieve a list of alarm with detail.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
"""
# /detail should only work agaist collections
parent = pecan.request.path.split('/')[:-1][-1]
if parent != "alarm":
raise exceptions.HTTPNotFound
expand = True
resource_url = '/'.join(['alarm', 'detail'])
return self._get_alarm_collection(marker, limit, sort_key, sort_dir,
expand, resource_url)
@wsme_pecan.wsexpose(Alarm, wtypes.text)
def get_one(self, id):
"""Retrieve information about the given alarm.
:param id: UUID of an alarm.
"""
rpc_ialarm = objects.alarm.get_by_uuid(
pecan.request.context, id)
if str(rpc_ialarm['masked']) == 'True':
raise exceptions.HTTPNotFound
return Alarm.convert_with_links(rpc_ialarm)
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, id):
"""Delete an alarm.
:param id: uuid of an alarm.
"""
data = pecan.request.dbapi.alarm_get(id)
if data is None:
raise wsme.exc.ClientSideError(_("can not find record to clear!"))
pecan.request.dbapi.alarm_destroy(id)
alarm_state = fm_constants.FM_ALARM_STATE_CLEAR
tmp_dict = data.as_dict()
self._alarm_save2event_log(tmp_dict, alarm_state, empty_uuid=True)
@wsme_pecan.wsexpose(AlarmSummary, bool)
def summary(self, include_suppress=False):
"""Retrieve a summery of alarms.
:param include_suppress: filter on suppressed alarms. Default: False
"""
return self._get_alarm_summary(include_suppress)
def _alarm_save2event_log(self, data_dict, fm_state, empty_uuid=False):
event_log_data = self._get_event_log_data(data_dict)
event_log_data['state'] = fm_state
event_log_data['id'] = None
if empty_uuid is True:
event_log_data['uuid'] = None
if (event_log_data['timestamp'] is None or
fm_state == fm_constants.FM_ALARM_STATE_CLEAR):
event_log_data['timestamp'] = datetime.datetime.utcnow()
event_data = pecan.request.dbapi.event_log_create(event_log_data)
return event_data
@wsme_pecan.wsexpose(wtypes.text, body=Alarm)
def post(self, alarm_data):
"""Create an alarm/event log.
:param alarm_data: All information required to create an
alarm or eventlog.
"""
alarm_data_dict = alarm_data.as_dict()
alarm_state = alarm_data_dict['alarm_state']
try:
if alarm_state == fm_constants.FM_ALARM_STATE_SET:
data = pecan.request.dbapi.alarm_create(alarm_data_dict)
tmp_dict = data.as_dict()
self._alarm_save2event_log(tmp_dict, alarm_state)
elif (
alarm_state == fm_constants.FM_ALARM_STATE_LOG or
alarm_state == fm_constants.FM_ALARM_STATE_MSG
):
data = self._alarm_save2event_log(alarm_data_dict, 'log')
# This is same action as DELETE Method if para is uuid
# keep this RESTful for future use to clear/delete alarm with parameters
# are alarm_id and entity_instance_id
elif alarm_state == fm_constants.FM_ALARM_STATE_CLEAR:
clear_uuid = alarm_data_dict['uuid']
alarm_id = alarm_data_dict['alarm_id']
entity_instance_id = alarm_data_dict['entity_instance_id']
if clear_uuid is not None:
data = pecan.request.dbapi.alarm_get(clear_uuid)
pecan.request.dbapi.alarm_destroy(clear_uuid)
tmp_dict = data.as_dict()
self._alarm_save2event_log(tmp_dict, alarm_state, empty_uuid=True)
elif alarm_id is not None and entity_instance_id is not None:
data = pecan.request.dbapi.alarm_get_by_ids(alarm_id, entity_instance_id)
if data is None:
raise wsme.exc.ClientSideError(_("can not find record to clear!"))
pecan.request.dbapi.alarm_destroy_by_ids(alarm_id, entity_instance_id)
tmp_dict = data.as_dict()
self._alarm_save2event_log(tmp_dict, alarm_state, empty_uuid=True)
else:
msg = _("The alarm_state %s does not support!")
raise wsme.exc.ClientSideError(msg % alarm_state)
except Exception as err:
return err
alarm_dict = data.as_dict()
return json.dumps({"uuid": alarm_dict['uuid']})
@wsme_pecan.wsexpose(wtypes.text, wtypes.text, body=Alarm)
def put(self, id, alarm_data):
""" Update an alarm
:param id: uuid of an alarm.
:param alarm_data: Information to be updated
"""
alarm_data_dict = alarm_data.as_dict()
try:
alm = pecan.request.dbapi.alarm_update(id, alarm_data_dict)
except Exception as err:
return err
alarm_dict = alm.as_dict()
return json.dumps({"uuid": alarm_dict['uuid']})