You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
449 lines
16 KiB
449 lines
16 KiB
#
|
|
# 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 = 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 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']})
|