Add PUT/POST methods to FM restful API

This commit implements the methods PUT and POST in order
to insert and update alarms.

Story: 2004008
Task: 30270

Change-Id: I773e651165b3654684b95463167fc565aef1ffa4
Co-authored-by: Sun Austin <austin.sun@intel.com>
Signed-off-by: Mario Alfredo Carrillo Arevalo <mario.alfredo.c.arevalo@intel.com>
Signed-off-by: Sun Austin <austin.sun@intel.com>
This commit is contained in:
Mario Alfredo Carrillo Arevalo 2019-05-15 19:07:47 +00:00 committed by Sun Austin
parent 0de5931685
commit 1e2d8dc04c
9 changed files with 216 additions and 18 deletions

View File

@ -321,6 +321,7 @@ FM_LOG_ID_SW_UPGRADE_AUTO_APPLY_ABORTED = ALARM_GROUP_SW_MGMT + ".221"
FM_ALARM_STATE_SET = 'set' FM_ALARM_STATE_SET = 'set'
FM_ALARM_STATE_CLEAR = 'clear' FM_ALARM_STATE_CLEAR = 'clear'
FM_ALARM_STATE_MSG = 'msg' FM_ALARM_STATE_MSG = 'msg'
FM_ALARM_STATE_LOG = 'log'
FM_ALARM_TYPE_0 = 'other' FM_ALARM_TYPE_0 = 'other'
FM_ALARM_TYPE_1 = 'communication' FM_ALARM_TYPE_1 = 'communication'
@ -425,7 +426,8 @@ ALARM_PROBABLE_CAUSE_75 = 'configuration-out-of-date'
ALARM_PROBABLE_CAUSE_76 = 'configuration-provisioning-required' ALARM_PROBABLE_CAUSE_76 = 'configuration-provisioning-required'
ALARM_PROBABLE_CAUSE_UNKNOWN = 'unknown' ALARM_PROBABLE_CAUSE_UNKNOWN = 'unknown'
ALARM_STATE = [FM_ALARM_STATE_SET, FM_ALARM_STATE_CLEAR, FM_ALARM_STATE_MSG] ALARM_STATE = [FM_ALARM_STATE_SET, FM_ALARM_STATE_CLEAR,
FM_ALARM_STATE_MSG, FM_ALARM_STATE_LOG]
ALARM_TYPE = [FM_ALARM_TYPE_0, FM_ALARM_TYPE_1, FM_ALARM_TYPE_2, ALARM_TYPE = [FM_ALARM_TYPE_0, FM_ALARM_TYPE_1, FM_ALARM_TYPE_2,
FM_ALARM_TYPE_3, FM_ALARM_TYPE_4, FM_ALARM_TYPE_5, FM_ALARM_TYPE_3, FM_ALARM_TYPE_4, FM_ALARM_TYPE_5,

View File

@ -30,6 +30,12 @@ sysinv_opts = [
version_info = pbr.version.VersionInfo('fm') version_info = pbr.version.VersionInfo('fm')
fm_opts = [
cfg.StrOpt('event_log_max_size',
default='4000',
help="the max size of event_log"),
]
# Pecan Application Configurations # Pecan Application Configurations
app = { app = {
'root': 'fm.api.controllers.root.RootController', 'root': 'fm.api.controllers.root.RootController',
@ -51,7 +57,7 @@ def init(args, **kwargs):
ks_loading.register_session_conf_options(cfg.CONF, ks_loading.register_session_conf_options(cfg.CONF,
sysinv_group.name) sysinv_group.name)
logging.register_options(cfg.CONF) logging.register_options(cfg.CONF)
cfg.CONF.register_opts(fm_opts)
cfg.CONF(args=args, project='fm', cfg.CONF(args=args, project='fm',
version='%%(prog)s %s' % version_info.release_string(), version='%%(prog)s %s' % version_info.release_string(),
**kwargs) **kwargs)
@ -65,3 +71,7 @@ def setup_logging():
{'prog': sys.argv[0], {'prog': sys.argv[0],
'version': version_info.release_string()}) 'version': version_info.release_string()})
LOG.debug("command line: %s", " ".join(sys.argv)) LOG.debug("command line: %s", " ".join(sys.argv))
def get_max_event_log():
return cfg.CONF.event_log_max_size

View File

@ -6,12 +6,14 @@
import datetime import datetime
import json
import pecan import pecan
from pecan import rest from pecan import rest
import wsme import wsme
from wsme import types as wtypes from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
from oslo_utils._i18n import _
from oslo_log import log from oslo_log import log
from fm_api import fm_api from fm_api import fm_api
@ -132,7 +134,8 @@ class Alarm(base.APIBase):
not fm_api.FaultAPIs.alarm_allowed(alm.severity, mgmt_affecting)) not fm_api.FaultAPIs.alarm_allowed(alm.severity, mgmt_affecting))
alm.degrade_affecting = str( alm.degrade_affecting = str(
not fm_api.FaultAPIs.alarm_allowed(alm.severity, degrade_affecting)) not fm_api.FaultAPIs.alarm_allowed(alm.severity,
degrade_affecting))
return alm return alm
@ -275,6 +278,28 @@ class AlarmController(rest.RestController):
sort_key=sort_key, sort_key=sort_key,
sort_dir=sort_dir) 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], @wsme_pecan.wsexpose(AlarmCollection, [Query],
types.uuid, int, wtypes.text, wtypes.text, bool, bool) types.uuid, int, wtypes.text, wtypes.text, bool, bool)
def get_all(self, q=[], marker=None, limit=None, sort_key='id', def get_all(self, q=[], marker=None, limit=None, sort_key='id',
@ -332,7 +357,13 @@ class AlarmController(rest.RestController):
:param id: uuid of 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) 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) @wsme_pecan.wsexpose(AlarmSummary, bool)
def summary(self, include_suppress=False): def summary(self, include_suppress=False):
@ -341,3 +372,77 @@ class AlarmController(rest.RestController):
:param include_suppress: filter on suppressed alarms. Default: False :param include_suppress: filter on suppressed alarms. Default: False
""" """
return self._get_alarm_summary(include_suppress) 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']})

View File

@ -7,12 +7,10 @@
import sys import sys
from oslo_config import cfg from oslo_config import cfg
cfg.CONF(sys.argv[1:], project='fm')
from fm.db import migration from fm.db import migration
CONF = cfg.CONF CONF = cfg.CONF
def main(): def main():
cfg.CONF(sys.argv[1:], project='fm')
migration.db_sync() migration.db_sync()

View File

@ -112,3 +112,7 @@ class Conflict(ApiError):
class AlarmAlreadyExists(Conflict): class AlarmAlreadyExists(Conflict):
message = _("An Alarm with UUID %(uuid)s already exists.") message = _("An Alarm with UUID %(uuid)s already exists.")
class EventLogAlreadyExists(Conflict):
message = _("An Eventlog with ID %(id)s already exists.")

View File

@ -112,6 +112,15 @@ class Connection(object):
""" """
@abc.abstractmethod
def event_log_create(self, values):
"""Create a new event_log.
:param values: A dict containing several items used to identify
and track the event_log.
:returns: An event_log.
"""
@abc.abstractmethod @abc.abstractmethod
def event_log_get(self, uuid): def event_log_get(self, uuid):
"""Return an event_log. """Return an event_log.

View File

@ -20,6 +20,7 @@ from oslo_db.sqlalchemy import session as db_session
from sqlalchemy import asc, desc, or_ from sqlalchemy import asc, desc, or_
from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.exc import NoResultFound
from fm.api import config
from fm.common import constants from fm.common import constants
from fm.common import exceptions from fm.common import exceptions
from fm.common import utils from fm.common import utils
@ -290,12 +291,10 @@ class Connection(api.Connection):
with _session_for_write() as session: with _session_for_write() as session:
query = model_query(models.Alarm, session=session) query = model_query(models.Alarm, session=session)
query = query.filter_by(uuid=id) query = query.filter_by(uuid=id)
try: try:
query.one() query.one()
except NoResultFound: except NoResultFound:
raise exceptions.AlarmNotFound(alarm=id) raise exceptions.AlarmNotFound(alarm=id)
query.delete() query.delete()
def alarm_destroy_by_ids(self, alarm_id, entity_instance_id): def alarm_destroy_by_ids(self, alarm_id, entity_instance_id):
@ -304,14 +303,52 @@ class Connection(api.Connection):
if alarm_id and entity_instance_id: if alarm_id and entity_instance_id:
query = query.filter_by(alarm_id=alarm_id) query = query.filter_by(alarm_id=alarm_id)
query = query.filter_by(entity_instance_id=entity_instance_id) query = query.filter_by(entity_instance_id=entity_instance_id)
try: try:
query.one() query.one()
except NoResultFound: except NoResultFound:
raise exceptions.AlarmNotFound(alarm=alarm_id) raise exceptions.AlarmNotFound(alarm=alarm_id)
query.delete() query.delete()
def event_log_create(self, values):
if not values.get('uuid'):
values['uuid'] = utils.generate_uuid()
event_log = models.EventLog()
event_log.update(values)
count = self.event_log_get_count()
max_log = config.get_max_event_log()
if count >= int(max_log):
self.delete_oldest_event_log()
with _session_for_write() as session:
try:
session.add(event_log)
session.flush()
except db_exc.DBDuplicateEntry:
raise exceptions.EventLogAlreadyExists(id=values['id'])
return event_log
def event_log_get_count(self):
query = model_query(models.EventLog)
return query.count()
def delete_oldest_event_log(self):
result = self.event_log_get_oldest()
self.event_log_delete(result['id'])
def event_log_delete(self, id):
with _session_for_write() as session:
query = model_query(models.EventLog, session=session)
query = query.filter_by(id=id)
try:
query.one()
except NoResultFound:
raise exceptions.EventLogNotFound(eventLog=id)
query.delete()
def event_log_get_oldest(self):
query = model_query(models.EventLog)
result = query.order_by(asc(models.EventLog.created_at)).limit(1).one()
return result
@objects.objectify(objects.event_log) @objects.objectify(objects.event_log)
def event_log_get(self, uuid): def event_log_get(self, uuid):
query = model_query(models.EventLog) query = model_query(models.EventLog)

View File

@ -7,8 +7,10 @@
from sqlalchemy import Column, MetaData, String, Table from sqlalchemy import Column, MetaData, String, Table
from sqlalchemy import Boolean, Integer, DateTime from sqlalchemy import Boolean, Integer, DateTime
from sqlalchemy.dialects.mysql import DATETIME
from sqlalchemy.schema import ForeignKeyConstraint from sqlalchemy.schema import ForeignKeyConstraint
from oslo_log import log
LOG = log.getLogger(__name__)
ENGINE = 'InnoDB' ENGINE = 'InnoDB'
CHARSET = 'utf8' CHARSET = 'utf8'
@ -37,21 +39,25 @@ def upgrade(migrate_engine):
mysql_charset=CHARSET, mysql_charset=CHARSET,
) )
event_suppression.create() event_suppression.create()
if migrate_engine.url.get_dialect().name == 'mysql':
LOG.info("alarm dialect is mysql")
timestamp_column = Column('timestamp', DATETIME(fsp=6))
else:
LOG.info("alarm dialect is others")
timestamp_column = Column('timestamp', DateTime(timezone=False))
alarm = Table( alarm = Table(
'alarm', 'alarm',
meta, meta,
Column('created_at', DateTime), Column('created_at', DateTime),
Column('updated_at', DateTime), Column('updated_at', DateTime),
Column('deleted_at', DateTime), Column('deleted_at', DateTime),
Column('id', Integer, primary_key=True, nullable=False), Column('id', Integer, primary_key=True, nullable=False),
Column('uuid', String(255), unique=True, index=True), Column('uuid', String(255), unique=True, index=True),
Column('alarm_id', String(255), index=True), Column('alarm_id', String(255), index=True),
Column('alarm_state', String(255)), Column('alarm_state', String(255)),
Column('entity_type_id', String(255), index=True), Column('entity_type_id', String(255), index=True),
Column('entity_instance_id', String(255), index=True), Column('entity_instance_id', String(255), index=True),
Column('timestamp', DateTime(timezone=False)), timestamp_column,
Column('severity', String(255), index=True), Column('severity', String(255), index=True),
Column('reason_text', String(255)), Column('reason_text', String(255)),
Column('alarm_type', String(255), index=True), Column('alarm_type', String(255), index=True),
@ -72,6 +78,12 @@ def upgrade(migrate_engine):
mysql_charset=CHARSET, mysql_charset=CHARSET,
) )
alarm.create() alarm.create()
if migrate_engine.url.get_dialect().name == 'mysql':
LOG.info("event_log dialect is mysql")
timestamp_column = Column('timestamp', DATETIME(fsp=6))
else:
LOG.info("event_log dialect is others")
timestamp_column = Column('timestamp', DateTime(timezone=False))
event_log = Table( event_log = Table(
'event_log', 'event_log',
@ -86,7 +98,7 @@ def upgrade(migrate_engine):
Column('state', String(255)), Column('state', String(255)),
Column('entity_type_id', String(255), index=True), Column('entity_type_id', String(255), index=True),
Column('entity_instance_id', String(255), index=True), Column('entity_instance_id', String(255), index=True),
Column('timestamp', DateTime(timezone=False)), timestamp_column,
Column('severity', String(255), index=True), Column('severity', String(255), index=True),
Column('reason_text', String(255)), Column('reason_text', String(255)),
Column('event_log_type', String(255), index=True), Column('event_log_type', String(255), index=True),

View File

@ -23,7 +23,6 @@
import json import json
from six.moves.urllib.parse import urlparse from six.moves.urllib.parse import urlparse
from oslo_config import cfg from oslo_config import cfg
from sqlalchemy import Column, ForeignKey, Integer, Boolean from sqlalchemy import Column, ForeignKey, Integer, Boolean
@ -32,6 +31,10 @@ from sqlalchemy import DateTime
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.types import TypeDecorator, VARCHAR from sqlalchemy.types import TypeDecorator, VARCHAR
from oslo_db.sqlalchemy import models from oslo_db.sqlalchemy import models
from sqlalchemy.dialects.mysql import DATETIME
from oslo_log import log
CONF = cfg.CONF
LOG = log.getLogger(__name__)
def table_args(): def table_args():
@ -42,6 +45,18 @@ def table_args():
return None return None
def get_dialect_name():
db_driver_name = 'mysql'
if CONF.database.connection is None:
LOG.error("engine_name is None")
return db_driver_name
engine_name = urlparse(CONF.database.connection).scheme
if engine_name is not None:
db_driver_name = engine_name.split("+", 1)[0]
LOG.info("db_drive_name is %s" % db_driver_name)
return db_driver_name
class JSONEncodedDict(TypeDecorator): class JSONEncodedDict(TypeDecorator):
"""Represents an immutable structure as a json-encoded string.""" """Represents an immutable structure as a json-encoded string."""
@ -83,7 +98,10 @@ class Alarm(Base):
alarm_state = Column(String(255)) alarm_state = Column(String(255))
entity_type_id = Column(String(255), index=True) entity_type_id = Column(String(255), index=True)
entity_instance_id = Column(String(255), index=True) entity_instance_id = Column(String(255), index=True)
timestamp = Column(DateTime(timezone=False)) if get_dialect_name() == 'mysql':
timestamp = Column(DATETIME(fsp=6))
else:
timestamp = Column(DateTime(timezone=False))
severity = Column(String(255), index=True) severity = Column(String(255), index=True)
reason_text = Column(String(255)) reason_text = Column(String(255))
alarm_type = Column(String(255), index=True) alarm_type = Column(String(255), index=True)
@ -106,7 +124,10 @@ class EventLog(Base):
state = Column(String(255)) state = Column(String(255))
entity_type_id = Column(String(255), index=True) entity_type_id = Column(String(255), index=True)
entity_instance_id = Column(String(255), index=True) entity_instance_id = Column(String(255), index=True)
timestamp = Column(DateTime(timezone=False)) if get_dialect_name() == 'mysql':
timestamp = Column(DATETIME(fsp=6))
else:
timestamp = Column(DateTime(timezone=False))
severity = Column(String(255), index=True) severity = Column(String(255), index=True)
reason_text = Column(String(255)) reason_text = Column(String(255))
event_log_type = Column(String(255), index=True) event_log_type = Column(String(255), index=True)