From: Jon Zhang Date: Tue, 28 May 2024 01:36:03 +0000 (+0000) Subject: Merge "Update chart to support the deployment without persistent database" X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=face7ded891f079361dc37656a0fe3f54585d303;hp=6be44fb91ba404cb50e714a5ca112789b3f7b23c;p=pti%2Fo2.git Merge "Update chart to support the deployment without persistent database" --- diff --git a/Dockerfile.localtest b/Dockerfile.localtest index 33fcaa8..7ced03b 100644 --- a/Dockerfile.localtest +++ b/Dockerfile.localtest @@ -1,7 +1,27 @@ -FROM python:3.11-slim-buster - -RUN apt-get update && apt-get install -y git gcc \ - vim curl procps ssh +FROM nexus3.onap.org:10001/onap/integration-python:12.0.0 +# https://nexus3.onap.org/#browse/search=keyword%3Dintegration-python:d406d405e4cfbf1186265b01088caf9a +# https://git.onap.org/integration/docker/onap-python/tree/Dockerfile + +USER root + +ARG user=orano2 +ARG group=orano2 + +# Create a group and user +RUN addgroup -S $group && adduser -S -D -h /home/$user $user $group && \ + chown -R $user:$group /home/$user && \ + mkdir /var/log/$user && \ + mkdir -p /src && \ + mkdir -p /configs/ && \ + mkdir -p /src/o2app/ && \ + mkdir -p /src/helm_sdk/ && \ + mkdir -p /etc/o2/ && \ + chown -R $user:$group /var/log/$user && \ + chown -R $user:$group /src && \ + chown -R $user:$group /configs && \ + chown -R $user:$group /etc/o2/ + +COPY requirements.txt requirements-test.txt requirements-stx.txt constraints.txt /tmp/ # in case git repo is not accessable RUN mkdir -p /cgtsclient && mkdir -p /distcloud-client @@ -9,49 +29,61 @@ COPY temp/config /cgtsclient/ COPY temp/distcloud-client /distcloud-client/ COPY temp/fault /faultclient/ -RUN pip install -e cgtsclient/sysinv/cgts-client/cgts-client/ \ - && pip install -e /distcloud-client/distributedcloud-client \ - && pip install -e /faultclient/python-fmclient/fmclient/ -# in case git repo is not accessable - -COPY requirements.txt constraints.txt requirements-test.txt /tmp/ - -RUN pip install -r /tmp/requirements.txt -c /tmp/constraints.txt \ - && pip install -r /tmp/requirements-test.txt - - -RUN mkdir -p /src COPY o2ims/ /src/o2ims/ COPY o2dms/ /src/o2dms/ COPY o2common/ /src/o2common/ - -RUN mkdir -p /src/o2app/ COPY o2app/ /src/o2app/ +COPY setup.py /src/ -RUN mkdir -p /src/helm_sdk/ COPY helm_sdk/ /src/helm_sdk/ -COPY setup.py /src/ - COPY configs/ /etc/o2/ +COPY configs/ /configs/ + +RUN apk add --no-cache \ + git \ + curl \ + bluez-dev \ + bzip2-dev \ + dpkg-dev dpkg \ + expat-dev \ + gcc \ + libc-dev \ + libffi-dev \ + libnsl-dev \ + libtirpc-dev \ + linux-headers \ + make \ + ncurses-dev \ + openssl-dev \ + pax-utils \ + sqlite-dev \ + tcl-dev \ + tk \ + tk-dev \ + util-linux-dev \ + xz-dev \ + zlib-dev + +RUN set -ex \ + && apk add --no-cache bash \ + && apk add --no-cache --virtual .fetch2-deps \ + && pip install -r /tmp/requirements.txt -c /tmp/constraints.txt \ + && pip install -r /tmp/requirements-test.txt \ + && pip install -e /cgtsclient/sysinv/cgts-client/cgts-client/ \ + && pip install -e /distcloud-client/distributedcloud-client \ + && pip install -e /faultclient/python-fmclient/fmclient/ \ + && pip install -e /src \ + && apk del --no-network .fetch2-deps -# RUN mkdir -p /helmsdk -# COPY temp/helmsdk /helmsdk/ -# # RUN git clone --depth 1 --branch master https://github.com/cloudify-incubator/cloudify-helm-plugin.git helmsdk -# COPY /helmsdk/helm_sdk /src/helm_sdk - -# RUN pip install -e /src COPY tests/ /tests/ -#RUN apt-get install -y procps vim - -#RUN apt-get install -y curl -RUN curl -O https://get.helm.sh/helm-v3.3.1-linux-amd64.tar.gz; -RUN tar -zxvf helm-v3.3.1-linux-amd64.tar.gz; cp linux-amd64/helm /usr/local/bin - RUN mkdir -p /etc/kubeconfig/ # COPY temp/kubeconfig/config /etc/kubeconfig/ RUN mkdir -p /var/log/orano2 WORKDIR /src + +# USER $user +ENV PYTHONHASHSEED=0 diff --git a/docker-compose.yml b/docker-compose.yml index 3d5b5e8..a624b56 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -85,9 +85,6 @@ services: - "5005:80" watcher: - build: - context: . - dockerfile: Dockerfile.localtest image: o2imsdms depends_on: - redis_pubsub diff --git a/o2app/entrypoints/redis_eventconsumer.py b/o2app/entrypoints/redis_eventconsumer.py index c95133a..db39daa 100644 --- a/o2app/entrypoints/redis_eventconsumer.py +++ b/o2app/entrypoints/redis_eventconsumer.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021 Wind River Systems, Inc. +# Copyright (C) 2021-2024 Wind River Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,15 +16,16 @@ import redis import json + from o2app import bootstrap -from o2common.config import config from o2common.adapter.notifications import SmoNotifications +from o2common.config import config +from o2common.helper import o2logging from o2dms.domain import commands from o2ims.domain import commands as imscmd from o2ims.domain.subscription_obj import Message2SMO, RegistrationMessage from o2ims.domain.alarm_obj import AlarmEvent2SMO -from o2common.helper import o2logging logger = o2logging.get_logger(__name__) r = redis.Redis(**config.get_redis_host_and_port()) @@ -49,6 +50,7 @@ def main(): pubsub.subscribe('DmsChanged') pubsub.subscribe('ResourceChanged') pubsub.subscribe('AlarmEventChanged') + pubsub.subscribe('AlarmEventPurged') for m in pubsub.listen(): try: @@ -138,6 +140,14 @@ def handle_changed(m, bus): eventtype=data['notificationEventType'], updatetime=data['updatetime'])) bus.handle(cmd) + elif channel == 'AlarmEventPurged': + datastr = m['data'] + data = json.loads(datastr) + logger.info('AlarmEventPurged with cmd:{}'.format(data)) + cmd = imscmd.PurgeAlarmEvent(data=AlarmEvent2SMO( + id=data['id'], eventtype=data['notificationEventType'], + updatetime=data['updatetime'])) + bus.handle(cmd) else: logger.info("unhandled:{}".format(channel)) diff --git a/o2app/service/handlers.py b/o2app/service/handlers.py index 9b4f49d..4ef0ef8 100644 --- a/o2app/service/handlers.py +++ b/o2app/service/handlers.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021 Wind River Systems, Inc. +# Copyright (C) 2021-2024 Wind River Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ from o2ims.service.auditor import ocloud_handler, dms_handler, \ pserver_dev_handler, agg_compute_handler, agg_network_handler,\ agg_storage_handler, agg_undefined_handler from o2ims.service.command import notify_handler, registration_handler,\ - notify_alarm_handler + notify_alarm_handler, purge_alarm_handler from o2ims.service.event import ocloud_event, resource_event, \ resource_pool_event, alarm_event, dms_event, resource_type_event @@ -63,6 +63,8 @@ EVENT_HANDLERS = { notify_resourcepool_change], events.AlarmEventChanged: [alarm_event.\ notify_alarm_event_change], + events.AlarmEventPurged: [alarm_event.\ + notify_alarm_event_purge], } # type: Dict[Type[events.Event], Callable] @@ -95,4 +97,5 @@ COMMAND_HANDLERS = { commands.PubMessage2SMO: notify_handler.notify_change_to_smo, commands.PubAlarm2SMO: notify_alarm_handler.notify_alarm_to_smo, commands.Register2SMO: registration_handler.registry_to_smo, + commands.PurgeAlarmEvent: purge_alarm_handler.purge_alarm_event, } # type: Dict[Type[commands.Command], Callable] diff --git a/o2ims/adapter/alarm_repository.py b/o2ims/adapter/alarm_repository.py index 483c73c..5986ad5 100644 --- a/o2ims/adapter/alarm_repository.py +++ b/o2ims/adapter/alarm_repository.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 Wind River Systems, Inc. +# Copyright (C) 2022-2024 Wind River Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ class AlarmEventRecordSqlAlchemyRepository(AlarmEventRecordRepository): return (count, result) def _update(self, alarm_event_record: alarm_obj.AlarmEventRecord): - self.session.add(alarm_event_record) + self.session.merge(alarm_event_record) def _delete(self, alarm_event_record_id): self.session.query(alarm_obj.AlarmEventRecord).filter_by( diff --git a/o2ims/adapter/clients/fault_client.py b/o2ims/adapter/clients/fault_client.py index 989edfc..74abe57 100644 --- a/o2ims/adapter/clients/fault_client.py +++ b/o2ims/adapter/clients/fault_client.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 Wind River Systems, Inc. +# Copyright (C) 2022-2024 Wind River Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -97,11 +97,15 @@ class StxAlarmClient(BaseClient): def _set_stx_client(self): self.driver.setFaultClient(self._pool_id) + def delete(self, id) -> alarmModel.FaultGenericModel: + return self.driver.deleteAlarm(id) + class StxEventClient(BaseClient): - def __init__(self, driver=None): + def __init__(self, uow: unit_of_work.AbstractUnitOfWork, driver=None): super().__init__() self.driver = driver if driver else StxFaultClientImp() + self.uow = uow def _get(self, id) -> alarmModel.FaultGenericModel: return self.driver.getEventInfo(id) @@ -112,6 +116,12 @@ class StxEventClient(BaseClient): def _set_stx_client(self): self.driver.setFaultClient(self._pool_id) + def suppression_list(self, alarm_id) -> List[alarmModel.FaultGenericModel]: + return self.driver.getSuppressionList(alarm_id) + + def suppress(self, id) -> alarmModel.FaultGenericModel: + return self.driver.suppressEvent(id) + # internal driver which implement client call to Stx Fault Management instance class StxFaultClientImp(object): @@ -212,6 +222,12 @@ class StxFaultClientImp(object): return alarmModel.FaultGenericModel( alarmModel.EventTypeEnum.ALARM, self._alarmconverter(alarm)) + def deleteAlarm(self, id) -> alarmModel.FaultGenericModel: + alarm = self.fmclient.alarm.delete(id) + logger.debug('delete alarm id ' + id + ':' + str(alarm.to_dict())) + return alarmModel.FaultGenericModel( + alarmModel.EventTypeEnum.ALARM, self._alarmconverter(alarm)) + def getEventList(self, **filters) -> List[alarmModel.FaultGenericModel]: events = self.fmclient.event_log.list(alarms=True, expand=True) [logger.debug( @@ -226,6 +242,27 @@ class StxFaultClientImp(object): return alarmModel.FaultGenericModel( alarmModel.EventTypeEnum.EVENT, self._eventconverter(event)) + def suppressEvent(self, id) -> alarmModel.FaultGenericModel: + patch = [dict(path='/' + 'suppression_status', value='suppressed', + op='replace')] + event = self.fmclient.event_suppression.update(id, patch) + logger.debug('suppressed event id ' + id + ':' + str(event.to_dict())) + return alarmModel.FaultGenericModel( + alarmModel.EventTypeEnum.EVENT, self._suppression_converter(event)) + + def getSuppressionList(self, alarm_id) -> alarmModel.FaultGenericModel: + suppression_list = [] + query_as_array = [] + events = self.fmclient.event_suppression.list(q=query_as_array) + for event in events: + if event.alarm_id == alarm_id: + # logger.debug('suppression event:' + str(event.to_dict())) + suppression_list.append( + alarmModel.FaultGenericModel( + alarmModel.EventTypeEnum.EVENT, + self._suppression_converter(event))) + return suppression_list + @ staticmethod def _alarmconverter(alarm): selected_keys = [ @@ -271,6 +308,27 @@ class StxFaultClientImp(object): uuid.NAMESPACE_URL, event.probable_cause))) return event + @ staticmethod + def _suppression_converter(event, clear=False): + selected_keys = [ + 'alarm_id', 'description', 'suppression_status', + 'links' + ] + content = event.to_dict() + filtered = dict( + filter(lambda item: item[0] in selected_keys, content.items())) + setattr(event, 'filtered', filtered) + setattr(event, 'uuid', event.uuid) + setattr(event, 'alarm_id', event.alarm_id) + setattr(event, 'description', event.description) + setattr(event, 'suppression_status', event.suppression_status) + setattr(event, 'alarm_type', None) + setattr(event, 'alarm_def_id', None) + setattr(event, 'probable_cause_id', None) + setattr(event, 'state', None) + setattr(event, 'timestamp', None) + return event + @ staticmethod def _alarmeventhasher(event, state=''): # The event model and the alarm model have different parameter name diff --git a/o2ims/domain/alarm_obj.py b/o2ims/domain/alarm_obj.py index 050e7b0..800c6d7 100644 --- a/o2ims/domain/alarm_obj.py +++ b/o2ims/domain/alarm_obj.py @@ -125,6 +125,14 @@ class ClearingTypeEnum(str, Enum): MANUAL = 'MANUAL' +class AlarmEventRecordModifications(AgRoot): + def __init__(self, ack: bool = None, + clear: PerceivedSeverityEnum = None) -> None: + super().__init__() + self.alarmAcknowledged = ack + self.perceivedSeverity = clear + + class AlarmDefinition(AgRoot, Serializer): def __init__(self, id: str, name: str, change_type: AlarmChangeTypeEnum, desc: str, prop_action: str, clearing_type: ClearingTypeEnum, @@ -170,9 +178,8 @@ class AlarmNotificationEventEnum(str, Enum): class AlarmEvent2SMO(Serializer): def __init__(self, eventtype: AlarmNotificationEventEnum, - id: str, ref: str, updatetime: str) -> None: + id: str, updatetime: str) -> None: self.notificationEventType = eventtype - self.objectRef = ref self.id = id self.updatetime = updatetime diff --git a/o2ims/domain/commands.py b/o2ims/domain/commands.py index 0caca37..9cc51d6 100644 --- a/o2ims/domain/commands.py +++ b/o2ims/domain/commands.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021 Wind River Systems, Inc. +# Copyright (C) 2021-2024 Wind River Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -138,3 +138,8 @@ class UpdatePserverAcc(UpdateResource): @dataclass class UpdateAlarm(UpdateFaultObject): parentid: str + + +@dataclass +class PurgeAlarmEvent(UpdateFaultObject): + data: AlarmEvent2SMO diff --git a/o2ims/domain/events.py b/o2ims/domain/events.py index ede48d2..04e8a08 100644 --- a/o2ims/domain/events.py +++ b/o2ims/domain/events.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021 Wind River Systems, Inc. +# Copyright (C) 2021-2024 Wind River Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -62,3 +62,10 @@ class AlarmEventChanged(Event): id: str notificationEventType: AlarmNotificationEventEnum updatetime: datetime.now() + + +@dataclass +class AlarmEventPurged(Event): + id: str + notificationEventType: AlarmNotificationEventEnum + updatetime: datetime.now() diff --git a/o2ims/service/command/purge_alarm_handler.py b/o2ims/service/command/purge_alarm_handler.py new file mode 100644 index 0000000..335488a --- /dev/null +++ b/o2ims/service/command/purge_alarm_handler.py @@ -0,0 +1,132 @@ +# Copyright (C) 2024 Wind River Systems, Inc. +# +# 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. + +import json + +from o2common.adapter.notifications import AbstractNotifications +from o2common.config import conf +from o2common.domain.filter import gen_orm_filter +from o2common.helper import o2logging +from o2common.service.unit_of_work import AbstractUnitOfWork +from o2ims.adapter.clients.fault_client import StxAlarmClient +from o2ims.domain import commands, alarm_obj +logger = o2logging.get_logger(__name__) + + +def purge_alarm_event( + cmd: commands.PubAlarm2SMO, + uow: AbstractUnitOfWork, + notifications: AbstractNotifications, +): + """ + Purges an alarm event and notifies relevant subscribers. + + This method performs the following steps: + 1. Retrieves data from the command object and initializes the fault client. + 2. Uses the Unit of Work pattern to find and delete the corresponding + alarm event record. + 3. Updates the alarm event record's hash, extensions, changed time, + and perceived severity. + 4. Commits the changes to the database. + 5. Finds and processes all alarm subscriptions, deciding whether to + send notifications based on subscription filters. + + Parameters: + - cmd (commands.PubAlarm2SMO): Command object containing the alarm + event data. + - uow (AbstractUnitOfWork): Unit of Work object for managing + database transactions. + - notifications (AbstractNotifications): Abstract notifications + object for sending notifications. + + Exceptions: + - Any exceptions that might occur during database operations or + notification sending. + """ + fault_client = StxAlarmClient(uow) + data = cmd.data + with uow: + alarm_event_record = uow.alarm_event_records.get(data.id) + alarm = fault_client.delete(alarm_event_record.alarmEventRecordId) + alarm_event_record.hash = alarm.hash + alarm_event_record.extensions = json.dumps(alarm.filtered) + alarm_event_record.alarmChangedTime = alarm.updatetime.\ + strftime("%Y-%m-%dT%H:%M:%S") + alarm_event_record.perceivedSeverity = \ + alarm_obj.PerceivedSeverityEnum.CLEARED + + uow.alarm_event_records.update(alarm_event_record) + + uow.commit() + + alarm = uow.alarm_event_records.get(data.id) + subs = uow.alarm_subscriptions.list() + for sub in subs: + sub_data = sub.serialize() + logger.debug('Alarm Subscription: {}'.format( + sub_data['alarmSubscriptionId'])) + + if not sub_data.get('filter', None): + callback_smo(notifications, sub, data, alarm) + continue + try: + args = gen_orm_filter(alarm_obj.AlarmEventRecord, + sub_data['filter']) + except KeyError: + logger.warning( + 'Alarm Subscription {} filter {} has wrong attribute ' + 'name or value. Ignore the filter'.format( + sub_data['alarmSubscriptionId'], + sub_data['filter'])) + callback_smo(notifications, sub, data, alarm) + continue + args.append(alarm_obj.AlarmEventRecord. + alarmEventRecordId == data.id) + count, _ = uow.alarm_event_records.list_with_count(*args) + if count != 0: + logger.debug( + 'Alarm Event {} skip for subscription {} because of ' + 'the filter.' + .format(data.id, sub_data['alarmSubscriptionId'])) + continue + callback_smo(notifications, sub, data, alarm) + + +def callback_smo(notifications: AbstractNotifications, + sub: alarm_obj.AlarmSubscription, + msg: alarm_obj.AlarmEvent2SMO, + alarm: alarm_obj.AlarmEventRecord): + sub_data = sub.serialize() + alarm_data = alarm.serialize() + callback = { + 'globalCloudID': conf.DEFAULT.ocloud_global_id, + 'consumerSubscriptionId': sub_data['consumerSubscriptionId'], + 'notificationEventType': msg.notificationEventType, + 'objectRef': msg.objectRef, + 'alarmEventRecordId': alarm_data['alarmEventRecordId'], + 'resourceTypeID': alarm_data['resourceTypeId'], + 'resourceID': alarm_data['resourceId'], + 'alarmDefinitionID': alarm_data['alarmDefinitionId'], + 'probableCauseID': alarm_data['probableCauseId'], + 'alarmRaisedTime': alarm_data['alarmRaisedTime'], + 'alarmChangedTime': alarm_data['alarmChangedTime'], + 'alarmAcknowledgeTime': alarm_data['alarmAcknowledgeTime'], + 'alarmAcknowledged': alarm_data['alarmAcknowledged'], + 'perceivedSeverity': alarm_data['perceivedSeverity'], + 'extensions': json.loads(alarm_data['extensions']) + } + logger.info('callback URL: {}'.format(sub_data['callback'])) + logger.debug('callback data: {}'.format(json.dumps(callback))) + + return notifications.send(sub_data['callback'], callback) diff --git a/o2ims/service/event/alarm_event.py b/o2ims/service/event/alarm_event.py index 5e80668..78807c7 100644 --- a/o2ims/service/event/alarm_event.py +++ b/o2ims/service/event/alarm_event.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021 Wind River Systems, Inc. +# Copyright (C) 2021-2024 Wind River Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -24,7 +24,15 @@ def notify_alarm_event_change( event: events.AlarmEventChanged, publish: Callable, ): - logger.debug('In notify_alarm_event_change') publish("AlarmEventChanged", event) logger.debug("published Alarm Event Changed: {}".format( event.id)) + + +def notify_alarm_event_purge( + event: events.AlarmEventPurged, + publish: Callable, +): + publish("AlarmEventPurged", event) + logger.debug("published Alarm Event Purged: {}".format( + event.id)) diff --git a/o2ims/views/alarm_dto.py b/o2ims/views/alarm_dto.py index 3028a84..caef205 100644 --- a/o2ims/views/alarm_dto.py +++ b/o2ims/views/alarm_dto.py @@ -129,6 +129,22 @@ class AlarmDTO: # 'alarmAcknowledgeTime,alarmAcknowledged,extensions}' ) + alarm_event_record_patch = api_monitoring_v1.model( + "AlarmPatchDto", + { + 'alarmAcknowledged': fields.Boolean( + example=True, + description='Boolean value indication of a management ' + + 'system has acknowledged the alarm.'), + 'perceivedSeverity': fields.String( + example='5', + description='indicate that the alarm record is requested ' + + 'to be cleared. Only the value "5" for "CLEARED" is ' + + 'permitted in a request message content. ') + }, + mask='{alarmAcknowledged,}' + ) + class SubscriptionDTO: diff --git a/o2ims/views/alarm_route.py b/o2ims/views/alarm_route.py index e7a6425..7b0c03e 100644 --- a/o2ims/views/alarm_route.py +++ b/o2ims/views/alarm_route.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021 Wind River Systems, Inc. +# Copyright (C) 2021-2024 Wind River Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ from o2common.service.messagebus import MessageBus from o2common.views.pagination_route import link_header, PAGE_PARAM from o2common.views.route_exception import NotFoundException, \ BadRequestException +from o2ims.domain.alarm_obj import PerceivedSeverityEnum from o2ims.views import alarm_view from o2ims.views.api_ns import api_ims_monitoring as api_monitoring_v1 from o2ims.views.alarm_dto import AlarmDTO, SubscriptionDTO, \ @@ -130,6 +131,7 @@ class AlarmListRouter(Resource): class AlarmGetRouter(Resource): model = AlarmDTO.alarm_event_record_get + patch = AlarmDTO.alarm_event_record_patch @api_monitoring_v1.doc('Get Alarm Event Record Information') @api_monitoring_v1.marshal_with(model) @@ -140,6 +142,39 @@ class AlarmGetRouter(Resource): raise NotFoundException( "Alarm Event Record {} doesn't exist".format(alarmEventRecordId)) + @api_monitoring_v1.doc('Patch Alarm Event Record Information') + @api_monitoring_v1.expect(patch) + @api_monitoring_v1.marshal_with(patch) + def patch(self, alarmEventRecordId): + data = api_monitoring_v1.payload + ack_action = data.get('alarmAcknowledged', None) + clear_action = data.get('perceivedSeverity', None) + + ack_is_none = ack_action is None + clear_is_none = clear_action is None + if (ack_is_none and clear_is_none) or (not ack_is_none and + not clear_is_none): + raise BadRequestException('Either "alarmAcknowledged" or ' + '"perceivedSeverity" shall be included ' + 'in a request, but not both.') + if ack_action: + result = alarm_view.alarm_event_record_ack(alarmEventRecordId, + bus.uow) + if result is not None: + return result + elif clear_action: + if clear_action != PerceivedSeverityEnum.CLEARED: + raise BadRequestException( + 'Only the value "5" for "CLEARED" is permitted of ' + '"perceivedSeverity".') + + result = alarm_view.alarm_event_record_clear(alarmEventRecordId, + bus.uow) + if result is not None: + return result + raise NotFoundException( + "Alarm Event Record {} doesn't exist".format(alarmEventRecordId)) + # ---------- Alarm Subscriptions ---------- # @api_monitoring_v1.route("/v1/alarmSubscriptions") diff --git a/o2ims/views/alarm_view.py b/o2ims/views/alarm_view.py index 83189c1..00d4a20 100644 --- a/o2ims/views/alarm_view.py +++ b/o2ims/views/alarm_view.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021 Wind River Systems, Inc. +# Copyright (C) 2021-2024 Wind River Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,16 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +from datetime import datetime import uuid as uuid -from o2common.service import unit_of_work +from o2common.service import unit_of_work, messagebus from o2common.views.view import gen_filter, check_filter from o2common.views.pagination_view import Pagination from o2common.views.route_exception import BadRequestException, \ NotFoundException +from o2ims.domain import events from o2ims.views.alarm_dto import SubscriptionDTO -from o2ims.domain.alarm_obj import AlarmSubscription, AlarmEventRecord +from o2ims.domain.alarm_obj import AlarmSubscription, AlarmEventRecord, \ + AlarmNotificationEventEnum, AlarmEventRecordModifications, \ + PerceivedSeverityEnum from o2common.helper import o2logging # from o2common.config import config @@ -45,6 +49,58 @@ def alarm_event_record_one(alarmEventRecordId: str, return first.serialize() if first is not None else None +def alarm_event_record_ack(alarmEventRecordId: str, + uow: unit_of_work.AbstractUnitOfWork): + with uow: + alarm_event_record = uow.alarm_event_records.get(alarmEventRecordId) + # Check the record does not exist, return None. Otherwise, the + # acknowledge request will update the record even if it is + # acknowledged. + if alarm_event_record is None: + return None + alarm_event_record.alarmAcknowledged = True + alarm_event_record.alarmAcknowledgeTime = datetime.\ + now().strftime("%Y-%m-%dT%H:%M:%S") + uow.alarm_event_records.update(alarm_event_record) + uow.commit() + + result = AlarmEventRecordModifications(True) + return result + + +def alarm_event_record_clear(alarmEventRecordId: str, + uow: unit_of_work.AbstractUnitOfWork): + with uow: + alarm_event_record = uow.alarm_event_records.get(alarmEventRecordId) + if alarm_event_record is None: + return None + elif alarm_event_record.perceivedSeverity == \ + PerceivedSeverityEnum.CLEARED: + raise BadRequestException( + "Alarm Event Record {} has already been marked as CLEARED." + .format(alarmEventRecordId)) + alarm_event_record.events.append(events.AlarmEventPurged( + id=alarm_event_record.alarmEventRecordId, + notificationEventType=AlarmNotificationEventEnum.CLEAR, + updatetime=alarm_event_record.alarmAcknowledgeTime)) + + uow.alarm_event_records.update(alarm_event_record) + uow.commit() + + result = AlarmEventRecordModifications( + clear=PerceivedSeverityEnum.CLEARED) + _handle_events(messagebus.MessageBus.get_instance()) + return result + + +def _handle_events(bus: messagebus.MessageBus): + # handle events + events = bus.uow.collect_new_events() + for event in events: + bus.handle(event) + return True + + def subscriptions(uow: unit_of_work.AbstractUnitOfWork, **kwargs): pagination = Pagination(**kwargs) query_kwargs = pagination.get_pagination() diff --git a/requirements-stx.txt b/requirements-stx.txt index d057d79..9f4f9f6 100644 --- a/requirements-stx.txt +++ b/requirements-stx.txt @@ -3,5 +3,6 @@ # -e git+https://opendev.org/starlingx/fault.git@master#egg=fmclient&subdirectory=python-fmclient/fmclient -e git+https://opendev.org/starlingx/distcloud-client.git@b4a8ec19dc6078952a3762d7eee8d426d520a1f0#egg=distributedcloud-client&subdirectory=distributedcloud-client --e git+https://opendev.org/starlingx/config.git@r/stx.7.0#egg=cgtsclient&subdirectory=sysinv/cgts-client/cgts-client --e git+https://opendev.org/starlingx/fault.git@r/stx.7.0#egg=fmclient&subdirectory=python-fmclient/fmclient +# Updated to lastest commit at May 24th 2024 +-e git+https://opendev.org/starlingx/config.git@04271215320b8ab9824be837ac5aae07883d363b#egg=cgtsclient&subdirectory=sysinv/cgts-client/cgts-client +-e git+https://opendev.org/starlingx/fault.git@b213c9155b2323831143977447f41efc2f84b76a#egg=fmclient&subdirectory=python-fmclient/fmclient \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index b653ae8..447351b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 Wind River Systems, Inc. +# Copyright (C) 2022-2024 Wind River Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ # pylint: disable=redefined-outer-name import shutil import subprocess +import sys import time from pathlib import Path @@ -29,6 +30,23 @@ from tenacity import retry, stop_after_delay from unittest.mock import MagicMock from mock_alchemy.mocking import UnifiedAlchemyMagicMock +# Mock cgtsclient, dcmanagerclient, fmclient +modules_to_mock = [ + 'cgtsclient', + 'cgtsclient.client', + 'cgtsclient.exc', + 'dcmanagerclient', + 'dcmanagerclient.api', + 'dcmanagerclient.api.client', + 'fmclient', + 'fmclient.client', + 'fmclient.common', + 'fmclient.common.exceptions' +] + +for module_name in modules_to_mock: + sys.modules[module_name] = MagicMock() + from o2app.bootstrap import bootstrap from o2ims.views import configure_namespace from o2app.adapter import unit_of_work