Merge "Update chart to support the deployment without persistent database"
authorJon Zhang <rong.zhang@windriver.com>
Tue, 28 May 2024 01:36:03 +0000 (01:36 +0000)
committerGerrit Code Review <gerrit@o-ran-sc.org>
Tue, 28 May 2024 01:36:03 +0000 (01:36 +0000)
16 files changed:
Dockerfile.localtest
docker-compose.yml
o2app/entrypoints/redis_eventconsumer.py
o2app/service/handlers.py
o2ims/adapter/alarm_repository.py
o2ims/adapter/clients/fault_client.py
o2ims/domain/alarm_obj.py
o2ims/domain/commands.py
o2ims/domain/events.py
o2ims/service/command/purge_alarm_handler.py [new file with mode: 0644]
o2ims/service/event/alarm_event.py
o2ims/views/alarm_dto.py
o2ims/views/alarm_route.py
o2ims/views/alarm_view.py
requirements-stx.txt
tests/conftest.py

index 33fcaa8..7ced03b 100644 (file)
@@ -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
index 3d5b5e8..a624b56 100644 (file)
@@ -85,9 +85,6 @@ services:
       - "5005:80"
 
   watcher:
-    build:
-      context: .
-      dockerfile: Dockerfile.localtest
     image: o2imsdms
     depends_on:
       - redis_pubsub
index c95133a..db39daa 100644 (file)
@@ -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.
 
 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))
 
index 9b4f49d..4ef0ef8 100644 (file)
@@ -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]
index 483c73c..5986ad5 100644 (file)
@@ -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(
index 989edfc..74abe57 100644 (file)
@@ -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
index 050e7b0..800c6d7 100644 (file)
@@ -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
 
index 0caca37..9cc51d6 100644 (file)
@@ -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
index ede48d2..04e8a08 100644 (file)
@@ -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 (file)
index 0000000..335488a
--- /dev/null
@@ -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)
index 5e80668..78807c7 100644 (file)
@@ -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))
index 3028a84..caef205 100644 (file)
@@ -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:
 
index e7a6425..7b0c03e 100644 (file)
@@ -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")
index 83189c1..00d4a20 100644 (file)
@@ -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.
 #  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()
index d057d79..9f4f9f6 100644 (file)
@@ -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
index b653ae8..447351b 100644 (file)
@@ -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