From 52815feec68f8b725e530086965e3ca666718774 Mon Sep 17 00:00:00 2001 From: Stavros-Anastasios Charismiadis Date: Tue, 1 Apr 2025 12:46:05 +0300 Subject: [PATCH 1/2] Add feature negotiation on Events API, change supported feature attribute in API response to match the negotiated one --- .../capif_events/core/events_apis.py | 66 ++++++++++++++----- .../capif_events/core/notifications.py | 20 +++++- .../capif_events/models/event_subscription.py | 12 ---- 3 files changed, 65 insertions(+), 33 deletions(-) diff --git a/services/TS29222_CAPIF_Events_API/capif_events/core/events_apis.py b/services/TS29222_CAPIF_Events_API/capif_events/core/events_apis.py index d6b0bdf2..a247226b 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/events_apis.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/events_apis.py @@ -11,6 +11,21 @@ from .responses import internal_server_error, not_found_error, make_response, ba from ..util import serialize_clean_camel_case, clean_empty, dict_to_camel_case +TOTAL_FEATURES = 4 +SUPPORTED_FEATURES_HEX = "c" + +def return_negotiated_supp_feat_dict(supp_feat): + + final_supp_feat = bin(int(supp_feat, 16) & int(SUPPORTED_FEATURES_HEX, 16))[2:].zfill(TOTAL_FEATURES)[::-1] + + return { + "NotificationTestEvent": True if final_supp_feat[0] == "1" else False, + "NotificationWebsocket": True if final_supp_feat[1] == "1" else False, + "EnhancedEventReport": True if final_supp_feat[2] == "1" else False, + "ApiStatusMonitoring": True if final_supp_feat[3] == "1" else False, + "Final": hex(int(final_supp_feat[::-1], 2))[2:] + } + class EventSubscriptionsOperations(Resource): def __check_subscriber_id(self, subscriber_id): @@ -32,7 +47,7 @@ class EventSubscriptionsOperations(Resource): return not_found_error(detail="Invoker or APF or AEF or AMF Not found", cause="Subscriber Not Found") return None - + def __check_event_filters(self, events, filters): current_app.logger.debug("Checking event filters.") valid_filters = { @@ -86,10 +101,11 @@ class EventSubscriptionsOperations(Resource): if isinstance(result, Response): return result - + + negotiated_supported_features = return_negotiated_supp_feat_dict(event_subscription.supported_features) + # Check if EnhancedEventReport is enabled and validate event filters - - if EventSubscription.return_supp_feat_dict(event_subscription.supported_features)["EnhancedEventReport"]: + if negotiated_supported_features["EnhancedEventReport"]: if event_subscription.event_filters: current_app.logger.debug(event_subscription.event_filters) result = self.__check_event_filters(event_subscription.events, clean_empty(event_subscription.to_dict()["event_filters"])) @@ -109,6 +125,10 @@ class EventSubscriptionsOperations(Resource): evnt = dict() evnt["subscriber_id"] = subscriber_id evnt["subscription_id"] = subscription_id + + # Edit supported_features field to the negotiated one + event_subscription.supported_features = negotiated_supported_features["Final"] + evnt.update(event_subscription.to_dict()) mycol.insert_one(evnt) @@ -159,13 +179,13 @@ class EventSubscriptionsOperations(Resource): exception= "An exception occurred in delete event" current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - + def put_event(self, event_subscription, subscriber_id, subscription_id): try: mycol = self.db.get_col_by_name(self.db.event_collection) current_app.logger.debug("Updating event subscription") - + if event_subscription.supported_features is None: return bad_request_error( detail="supportedFeatures not present in request", @@ -177,19 +197,25 @@ class EventSubscriptionsOperations(Resource): if isinstance(result, Response): return result - - if EventSubscription.return_supp_feat_dict(event_subscription.supported_features)["EnhancedEventReport"] and event_subscription.event_filters: - result = self.__check_event_filters(event_subscription.events, clean_empty(event_subscription.to_dict()["event_filters"])) - if isinstance(result, Response): - return result my_query = {'subscriber_id': subscriber_id, - 'subscription_id': subscription_id} + 'subscription_id': subscription_id} eventdescription = mycol.find_one(my_query) if eventdescription is None: current_app.logger.error("Event subscription not found") - return not_found_error(detail="Event subscription not exist", cause="Event API subscription id not found") + return not_found_error(detail="Event subscription not exist", + cause="Event API subscription id not found") + + negotiated_supported_features = return_negotiated_supp_feat_dict(event_subscription.supported_features) + + if negotiated_supported_features["EnhancedEventReport"] and event_subscription.event_filters: + result = self.__check_event_filters(event_subscription.events, clean_empty(event_subscription.to_dict()["event_filters"])) + if isinstance(result, Response): + return result + + event_subscription.supported_features = negotiated_supported_features["Final"] + body = event_subscription.to_dict() body["subscriber_id"] = subscriber_id @@ -202,11 +228,11 @@ class EventSubscriptionsOperations(Resource): res = make_response(object=serialize_clean_camel_case(event_subscription), status=200) return res - + except Exception as e: exception= "An exception occurred in updating event" current_app.logger.error(exception + "::" + str(e)) - return internal_server_error(detail=exception, cause=str(e)) + return internal_server_error(detail=exception, cause=str(e)) def patch_event(self, event_subscription, subscriber_id, subscription_id): @@ -228,7 +254,9 @@ class EventSubscriptionsOperations(Resource): current_app.logger.error("Event subscription not found") return not_found_error(detail="Event subscription not exist", cause="Event API subscription id not found") - if EventSubscription.return_supp_feat_dict(eventdescription.get("supported_features"))["EnhancedEventReport"]: + negotiated_supported_features = return_negotiated_supp_feat_dict(eventdescription.get("supported_features")) + + if negotiated_supported_features["EnhancedEventReport"]: if event_subscription.events and event_subscription.event_filters: result = self.__check_event_filters(event_subscription.events, clean_empty(event_subscription.to_dict()["event_filters"])) elif event_subscription.events and event_subscription.event_filters is None and eventdescription.get("event_filters", None): @@ -239,6 +267,8 @@ class EventSubscriptionsOperations(Resource): if isinstance(result, Response): return result + event_subscription.supported_features = negotiated_supported_features["Final"] + body = clean_empty(event_subscription.to_dict()) document = mycol.update_one(my_query, {"$set":body}) document = mycol.find_one(my_query) @@ -247,8 +277,8 @@ class EventSubscriptionsOperations(Resource): res = make_response(object=EventSubscription.from_dict(dict_to_camel_case(document)), status=200) return res - + except Exception as e: exception= "An exception occurred in patching event" current_app.logger.error(exception + "::" + str(e)) - return internal_server_error(detail=exception, cause=str(e)) + return internal_server_error(detail=exception, cause=str(e)) diff --git a/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py b/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py index ab769e51..451054b3 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py @@ -13,6 +13,20 @@ from util import serialize_clean_camel_case from .internal_event_ops import InternalEventOperations +TOTAL_FEATURES = 4 +SUPPORTED_FEATURES_HEX = "c" + +def return_negotiated_supp_feat_dict(supp_feat): + + final_supp_feat = bin(int(supp_feat, 16) & int(SUPPORTED_FEATURES_HEX, 16))[2:].zfill(TOTAL_FEATURES)[::-1] + + return { + "NotificationTestEvent": True if final_supp_feat[0] == "1" else False, + "NotificationWebsocket": True if final_supp_feat[1] == "1" else False, + "EnhancedEventReport": True if final_supp_feat[2] == "1" else False, + "ApiStatusMonitoring": True if final_supp_feat[3] == "1" else False, + "Final": hex(int(final_supp_feat[::-1], 2))[2:] + } class Notifications(): @@ -36,7 +50,7 @@ class Notifications(): data = EventNotification(sub["subscription_id"], events=event) event_detail_redis=redis_event.get('event_detail', None) if event_detail_redis is not None: - if EventSubscription.return_supp_feat_dict(sub["supported_features"])["EnhancedEventReport"]: + if return_negotiated_supp_feat_dict(sub["supported_features"])["EnhancedEventReport"]: event_detail={} current_app.logger.debug(f"event: {event_detail_redis}") @@ -54,13 +68,13 @@ class Notifications(): api_ids_list = event_filter.get("api_ids", None) if api_ids_list and event_detail_redis.get('apiIds', None)[0] in api_ids_list: event_detail["apiIds"]=event_detail_redis.get('apiIds', None) - if EventSubscription.return_supp_feat_dict(sub["supported_features"])["ApiStatusMonitoring"]: + if return_negotiated_supp_feat_dict(sub["supported_features"])["ApiStatusMonitoring"]: event_detail["serviceAPIDescriptions"]=event_detail_redis.get('serviceAPIDescriptions', None) else: continue else: event_detail["apiIds"]=event_detail_redis.get('apiIds', None) - if EventSubscription.return_supp_feat_dict(sub["supported_features"])["ApiStatusMonitoring"]: + if return_negotiated_supp_feat_dict(sub["supported_features"])["ApiStatusMonitoring"]: event_detail["serviceAPIDescriptions"]=event_detail_redis.get('serviceAPIDescriptions', None) elif event in ["SERVICE_API_UPDATE"]: if event_filter: diff --git a/services/TS29222_CAPIF_Events_API/capif_events/models/event_subscription.py b/services/TS29222_CAPIF_Events_API/capif_events/models/event_subscription.py index fef52865..af05c096 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/models/event_subscription.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/models/event_subscription.py @@ -62,18 +62,6 @@ class EventSubscription(Model): self._websock_notif_config = websock_notif_config self._supported_features = supported_features - @classmethod - def return_supp_feat_dict(cls, supp_feat): - TOTAL_FEATURES=4 - supp_feat_in_hex = int(supp_feat, 16) - supp_feat_in_bin = bin(supp_feat_in_hex)[2:].zfill(TOTAL_FEATURES)[::-1] - - return { - "NotificationTestEvent": True if supp_feat_in_bin[0] == "1" else False, - "NotificationWebsocket": True if supp_feat_in_bin[1] == "1" else False, - "EnhancedEventReport": True if supp_feat_in_bin[2] == "1" else False, - "ApiStatusMonitoring": True if supp_feat_in_bin[3] == "1" else False - } @classmethod def from_dict(cls, dikt) -> 'EventSubscription': -- GitLab From 2f2d04f0a80f66b7089a11032332b77d83774b53 Mon Sep 17 00:00:00 2001 From: Stavros-Anastasios Charismiadis Date: Wed, 2 Apr 2025 14:11:00 +0300 Subject: [PATCH 2/2] Add check when EnhancedEventReport is deactivated and events filters exist --- .../capif_events/core/events_apis.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/TS29222_CAPIF_Events_API/capif_events/core/events_apis.py b/services/TS29222_CAPIF_Events_API/capif_events/core/events_apis.py index a247226b..7f60bc24 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/events_apis.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/events_apis.py @@ -213,6 +213,13 @@ class EventSubscriptionsOperations(Resource): result = self.__check_event_filters(event_subscription.events, clean_empty(event_subscription.to_dict()["event_filters"])) if isinstance(result, Response): return result + elif (not negotiated_supported_features["EnhancedEventReport"]) and event_subscription.event_filters: + current_app.logger.error("Event filters provided but EnhancedEventReport is not enabled") + return bad_request_error( + detail="Bad Param", + cause="Event filters provided but EnhancedEventReport is not enabled", + invalid_params=[{"param": "eventFilters", "reason": "EnhancedEventReport is not enabled"}] + ) event_subscription.supported_features = negotiated_supported_features["Final"] -- GitLab