Merge "Fix: Fix typo with project key for Sonar"
authorJessica Wagantall <jwagantall@linuxfoundation.org>
Tue, 30 Apr 2024 00:58:49 +0000 (00:58 +0000)
committerGerrit Code Review <gerrit@o-ran-sc.org>
Tue, 30 Apr 2024 00:58:49 +0000 (00:58 +0000)
15 files changed:
charts/resources/scripts/init/o2api_start.sh
o2app/entrypoints/redis_eventconsumer.py
o2app/service/handlers.py
o2common/config/config.py
o2ims/adapter/alarm_repository.py
o2ims/adapter/clients/fault_client.py
o2ims/domain/alarm_repo.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_route.py
o2ims/views/alarm_view.py
tests/conftest.py
tests/o2app-api-entry2.sh

index 243d86c..4581db5 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2021-2022 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.
@@ -14,6 +14,7 @@
 
 #!/bin/bash
 
-gunicorn -b 0.0.0.0:80 o2app.entrypoints.flask_application:app --certfile /configs/server.crt  --keyfile /configs/server.key
+# The gunicorn start with [::] to listen on both IPv4 and IPv6
+gunicorn -b [::]:80 o2app.entrypoints.flask_application:app --certfile /configs/server.crt  --keyfile /configs/server.key
 
 sleep infinity
index c95133a..439619b 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.
@@ -49,6 +49,7 @@ def main():
     pubsub.subscribe('DmsChanged')
     pubsub.subscribe('ResourceChanged')
     pubsub.subscribe('AlarmEventChanged')
+    pubsub.subscribe('AlarmEventPurged')
 
     for m in pubsub.listen():
         try:
@@ -138,6 +139,17 @@ 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))
+        ref = api_monitoring_base + \
+            monitor_api_version + '/alarms/' + data['id']
+        cmd = imscmd.PurgeAlarmEvent(data=AlarmEvent2SMO(
+            id=data['id'], ref=ref,
+            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 c93d388..61c8c69 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2021-2022 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.
@@ -14,6 +14,7 @@
 
 import os
 import sys
+import ipaddress
 from urllib.parse import urlparse
 
 from o2common import config
@@ -117,6 +118,15 @@ def get_stx_client_args():
     return client_args
 
 
+def is_ipv6(address):
+    try:
+        # Try to convert the address and check the IP version
+        ip = ipaddress.ip_address(address)
+        return ip.version == 6
+    except ValueError:
+        return False
+
+
 def get_stx_access_info(region_name="RegionOne", subcloud_hostname: str = "",
                         sub_is_https: bool = False):
     # authurl = os.environ.get("STX_AUTH_URL", "http://192.168.204.1:5000/v3")
@@ -140,6 +150,8 @@ def get_stx_access_info(region_name="RegionOne", subcloud_hostname: str = "",
     for key, val in client_args.items():
         os_client_args['os_{key}'.format(key=key)] = val
     if "" != subcloud_hostname:
+        if is_ipv6(subcloud_hostname):
+            subcloud_hostname = "[" + subcloud_hostname + "]"
         orig_auth_url = urlparse(_DEFAULT_STX_URL)
         new_auth_url = orig_auth_url._replace(
             netloc=orig_auth_url.netloc.replace(
@@ -181,9 +193,11 @@ def get_dc_access_info():
     for key, val in client_args.items():
         os_client_args['os_{key}'.format(key=key)] = val
     auth_url = urlparse(os_client_args.pop('os_auth_url'))
+    hostname = f"[{auth_url.hostname}]" if is_ipv6(auth_url.hostname) \
+        else auth_url.hostname
     dcmanager_url = urlparse(_DEFAULT_DCMANAGER_URL)
     dcmanager_url = dcmanager_url._replace(netloc=dcmanager_url.netloc.replace(
-        dcmanager_url.hostname, auth_url.hostname))
+        dcmanager_url.hostname, hostname))
 
     os_client_args['dcmanager_url'] = dcmanager_url.geturl()
     os_client_args['auth_url'] = auth_url.geturl()
@@ -219,6 +233,8 @@ def get_fm_access_info(subcloud_hostname: str = "",
     os_client_args['auth_url'] = auth_url.geturl()
 
     if "" != subcloud_hostname:
+        subcloud_hostname = f"[{subcloud_hostname}]" if \
+            is_ipv6(subcloud_hostname) else subcloud_hostname
         orig_auth_url = urlparse(_DEFAULT_STX_URL)
         new_auth_url = orig_auth_url._replace(
             netloc=orig_auth_url.netloc.replace(
index 483c73c..ebfe816 100644 (file)
@@ -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 53602b4..c9e1e95 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.
@@ -94,9 +94,10 @@ class StxAlarmClient(BaseClient):
 
 
 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)
@@ -107,6 +108,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):
@@ -219,6 +226,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 = []
+        queryAsArray = []
+        events = self.fmclient.event_suppression.list(q=queryAsArray)
+        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 = [
@@ -264,6 +292,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 ca6929e..1ef92a4 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.
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..390cfe2
--- /dev/null
@@ -0,0 +1,51 @@
+# 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.service.unit_of_work import AbstractUnitOfWork
+from o2common.adapter.notifications import AbstractNotifications
+
+from o2ims.adapter.clients.fault_client import StxEventClient
+from o2ims.domain import commands, alarm_obj
+
+from o2common.helper import o2logging
+logger = o2logging.get_logger(__name__)
+
+
+def purge_alarm_event(
+    cmd: commands.PubAlarm2SMO,
+    uow: AbstractUnitOfWork,
+    notifications: AbstractNotifications,
+):
+    logger.debug('In purge_alarm_event')
+    fault_client = StxEventClient(uow)
+    data = cmd.data
+    with uow:
+        alarm_event_record = uow.alarm_event_records.get(data.id)
+        alarm_id = json.loads(alarm_event_record.extensions).get('alarm_id')
+
+        events = fault_client.suppression_list(alarm_id)
+        for event_id in events:
+            event = fault_client.suppress(event_id.id)
+            alarm_event_record.hash = event.hash
+            alarm_event_record.extensions = json.dumps(event.filtered)
+            alarm_event_record.alarmChangedTime = event.updatetime.\
+                strftime("%Y-%m-%dT%H:%M:%S")
+            alarm_event_record.perceivedSeverity = \
+                alarm_obj.PerceivedSeverityEnum.CLEARED
+
+            uow.alarm_event_records.update(alarm_event_record)
+            break
+        uow.commit()
index 5e80668..ff005d6 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.
@@ -28,3 +28,13 @@ def 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,
+):
+    logger.debug('In notify_alarm_event_purge')
+    publish("AlarmEventPurged", event)
+    logger.debug("published Alarm Event Purged: {}".format(
+        event.id))
index e7a6425..8b0af04 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.
@@ -140,6 +140,15 @@ 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.marshal_with(model)
+    def patch(self, alarmEventRecordId):
+        result = alarm_view.alarm_event_record_ack(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..9f7af04 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
 
 from o2common.helper import o2logging
 # from o2common.config import config
@@ -45,6 +48,41 @@ 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)
+        if alarm_event_record is None:
+            return None
+        elif alarm_event_record.alarmAcknowledged == 'true':
+            raise BadRequestException(
+                "Alarm Event Record {} has already been acknowledged."
+                .format(alarmEventRecordId))
+        alarm_event_record.alarmAcknowledged = True
+        alarm_event_record.alarmAcknowledgeTime = datetime.\
+            now().strftime("%Y-%m-%dT%H:%M:%S")
+        bus = messagebus.MessageBus.get_instance()
+        alarm_event_record.events.append(events.AlarmEventPurged(
+            id=alarm_event_record.alarmEventRecordId,
+            notificationEventType=AlarmNotificationEventEnum.ACKNOWLEDGE,
+            updatetime=alarm_event_record.alarmAcknowledgeTime))
+
+        uow.alarm_event_records.update(alarm_event_record)
+        uow.commit()
+
+        result = alarm_event_record.serialize()
+    _handle_events(bus)
+    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 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
index f343f83..f9654c5 100644 (file)
@@ -23,7 +23,7 @@ then
 cp /tests/my-root-ca-cert.pem /configs/my-root-ca-cert.pem
 cp /tests/my-server-cert.pem /configs/server.crt
 cp /tests/my-server-key.pem /configs/server.key
-gunicorn -b 0.0.0.0:80 o2app.entrypoints.flask_application:app --certfile /configs/server.crt  --keyfile /configs/server.key --log-level debug
+gunicorn -b [::]:80 o2app.entrypoints.flask_application:app --certfile /configs/server.crt  --keyfile /configs/server.key --log-level debug
 else
-gunicorn -b 0.0.0.0:80 o2app.entrypoints.flask_application:app --log-level debug
+gunicorn -b [::]:80 o2app.entrypoints.flask_application:app --log-level debug
 fi