From c5507ba95dd0a57e31d1948b2e5f08a59d02fc10 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Mon, 29 Apr 2024 18:36:03 +0200 Subject: [PATCH 01/26] Modify loggin to allow redis communication --- .../api_invocation_logs/core/invocationlogs.py | 9 +++++++++ .../api_invocation_logs/core/publisher.py | 11 +++++++++++ .../requirements.txt | 1 + tests/libraries/api_logging_service/bodyRequests.py | 6 +++--- 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/publisher.py diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py index dde0c64..aab8309 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py @@ -13,6 +13,9 @@ from ..util import dict_to_camel_case, clean_empty from .resources import Resource from .responses import bad_request_error, internal_server_error, forbidden_error, not_found_error, unauthorized_error, make_response from ..models.invocation_log import InvocationLog +from .publisher import Publisher + +publisher_ops = Publisher() class LoggingInvocationOperations(Resource): @@ -84,6 +87,12 @@ class LoggingInvocationOperations(Resource): if result is not None: return result + + if log.result: + if int(log.result) >= 200 and int(log.result) < 300: + publisher_ops.publish_message("events", "SERVICE_API_INVOCATION_SUCCESS") + else: + publisher_ops.publish_message("events", "SERVICE_API_INVOCATION_FAILURE") current_app.logger.debug("Check existing logs") my_query = {'aef_id': aef_id, 'api_invoker_id': invocationlog.api_invoker_id} diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/publisher.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/publisher.py new file mode 100644 index 0000000..3898c4b --- /dev/null +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/publisher.py @@ -0,0 +1,11 @@ +import redis +import sys +from flask import current_app + +class Publisher(): + + def __init__(self): + self. r = redis.Redis(host='redis', port=6379, db=0) + + def publish_message(self, channel, message): + self.r.publish(channel, message) diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/requirements.txt b/services/TS29222_CAPIF_Logging_API_Invocation_API/requirements.txt index 197399f..0dfa8b6 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/requirements.txt +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/requirements.txt @@ -6,6 +6,7 @@ Flask == 2.0.3 pymongo == 4.0.1 elasticsearch == 8.4.3 flask_jwt_extended == 4.4.4 +redis == 4.5.4 opentelemetry-instrumentation == 0.38b0 opentelemetry-instrumentation-flask == 0.38b0 opentelemetry-instrumentation-redis == 0.38b0 diff --git a/tests/libraries/api_logging_service/bodyRequests.py b/tests/libraries/api_logging_service/bodyRequests.py index fe3faf1..fc2cad4 100644 --- a/tests/libraries/api_logging_service/bodyRequests.py +++ b/tests/libraries/api_logging_service/bodyRequests.py @@ -1,4 +1,4 @@ -def create_log_entry(aefId=None, apiInvokerId=None, apiId=None, apiName=None): +def create_log_entry(aefId, apiInvokerId, apiId, apiName, result='200'): data = { "aefId": aefId, "apiInvokerId": apiInvokerId, @@ -11,7 +11,7 @@ def create_log_entry(aefId=None, apiInvokerId=None, apiId=None, apiName=None): "uri": "http://resource/endpoint", "protocol": "HTTP_1_1", "operation": "GET", - "result": "string", + "result": result, "invocationTime": "2023-03-30T10:30:21.408Z", "invocationLatency": 0, "inputParameters": "string", @@ -78,7 +78,7 @@ def create_log_entry(aefId=None, apiInvokerId=None, apiId=None, apiName=None): } return data -def create_log_entry_bad_service(aefId=None, apiInvokerId=None): +def create_log_entry_bad_service(aefId, apiInvokerId): data = { "aefId": aefId, "apiInvokerId": apiInvokerId, -- GitLab From 78fd87c516ede5a55bf06bb9cb8625d3c1aebbeb Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Mon, 29 Apr 2024 18:38:10 +0200 Subject: [PATCH 02/26] modify robot docker image --- .../CAPIF Api Events/capif_events_api.robot | 44 +++++++++++++++++++ tests/requirements.txt | 8 +--- tools/robot/Dockerfile | 24 ++++++++-- tools/robot/basicRequirements.txt | 13 +++--- tools/robot/basicRobotInstall.sh | 11 ++++- 5 files changed, 81 insertions(+), 19 deletions(-) diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index f2a5966..aceeb1b 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -5,6 +5,7 @@ Library XML Resource /opt/robot-tests/tests/resources/common/basicRequests.robot Resource ../../resources/common.resource + Suite Teardown Reset Testing Environment Test Setup Reset Testing Environment @@ -137,3 +138,46 @@ Deletes an individual CAPIF Event Subscription with invalid SubscriptionId ... title=Unauthorized ... detail=User not authorized ... cause=You are not the owner of this resource + +Prueba JMS + [Tags] jms-1 + # Log "Prueba 1" + # Wait For Request + # Create a log entry + # [Tags] capif_api_logging_service-1 + + # Register APF + ${register_user_info}= Provider Default Registration + + # Publish one api + Publish Service Api ${register_user_info} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Create Log Entry + ${request_body}= Create Log Entry + ... ${register_user_info['aef_id']} + ... ${register_user_info_invoker['api_invoker_id']} + ... ${api_ids} + ... ${api_names} + ... '200' + ${resp}= Post Request Capif + ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 InvocationLog + ${resource_url}= Check Location Header ${resp} ${LOCATION_LOGGING_RESOURCE_REGEX} + diff --git a/tests/requirements.txt b/tests/requirements.txt index c6d9032..b983217 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,8 +1,2 @@ # Requirements file for tests. -robotframework-mongodb-library==3.2 -requests==2.28.1 -configparser==5.3.0 -redis==4.3.4 -rfc3987==1.3.8 -robotframework-httpctrl -robotframework-archivelibrary == 0.4.2 \ No newline at end of file +robotframework-archivelibrary == 0.4.2 diff --git a/tools/robot/Dockerfile b/tools/robot/Dockerfile index 49cea17..261b1e4 100644 --- a/tools/robot/Dockerfile +++ b/tools/robot/Dockerfile @@ -25,6 +25,7 @@ VOLUME $ROBOT_RESULTS_DIRECTORY WORKDIR $ROBOT_DIRECTORY ENV DEBIAN_FRONTEND=noninteractive +RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections # Install dependencies RUN apt-get update @@ -49,17 +50,32 @@ RUN apt-get install -y --no-install-recommends \ python3-venv \ python2.7-dev \ libssl-dev \ - libldap2-dev libsasl2-dev ldap-utils slapd tox lcov valgrind\ - tshark + libldap2-dev libsasl2-dev ldap-utils slapd tox lcov valgrind \ + tshark \ + nodejs \ + npm -RUN add-apt-repository ppa:deadsnakes/ppa +RUN add-apt-repository -y ppa:deadsnakes/ppa RUN apt-get update RUN apt-get install -y --fix-missing python3.10 python3.10-venv python3.10-dev - RUN mkdir /opt/venv RUN python3.10 -m venv /opt/venv +ENV PLAYWRIGHT_BROWSERS_PATH=$HOME/pw-browsers + +# Instalación de nvm y node a la última versión +# RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash +# # RUN source /root/.bashrc +# # RUN export NVM_DIR="$HOME/.nvm" \ +# # [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm \ +# # [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion + +# RUN nvm install node +# # RUN npx playwright install +# RUN yes|npx playwright install-deps + + ADD basicRequirements.txt /root/ ADD basicRobotInstall.sh /root/ diff --git a/tools/robot/basicRequirements.txt b/tools/robot/basicRequirements.txt index 8775227..99eafd7 100644 --- a/tools/robot/basicRequirements.txt +++ b/tools/robot/basicRequirements.txt @@ -13,7 +13,7 @@ certifi==2021.10.8 cffi==1.15.1 chardet==5.0.0 charset-normalizer==2.0.12 -click==8.0.1 +click==8.1.7 configparser==5.3.0 cookiecutter==2.1.1 coverage==4.5.4 @@ -26,7 +26,6 @@ exceptiongroup==1.0.0rc9 filelock==3.8.0 flake8==3.9.2 h11==0.14.0 -robotframework-httpctrl==0.3.1 idna==3.4 iniconfig==1.1.1 invoke==1.6.0 @@ -69,10 +68,12 @@ redis==4.3.4 rellu==0.7 requests==2.28.1 rfc3987==1.3.8 -robotframework==6.0 +robotframework==7.0 +robotframework-browser==18.3.0 +robotframework-httpctrl==0.3.1 robotframework-lint==1.1 robotframework-mongodb-library==3.2 -robotframework-pythonlibcore==3.0.0 +robotframework-pythonlibcore==4.4.1 robotframework-requests==0.9.3 robotframework-seleniumlibrary==6.0.0 robotframework-sshlibrary==3.8.0 @@ -91,11 +92,11 @@ tox==3.26.0 tqdm==4.64.1 trio==0.22.0 trio-websocket==0.9.2 -typing-extensions==3.10.0.2 +typing-extensions==4.11.0 urllib3==1.26.12 virtualenv==20.16.5 watchdog==0.9.0 webdrivermanager==0.10.0 -wrapt==1.14.1 +wrapt==1.15.0 wsproto==1.2.0 xlrd==2.0.1 \ No newline at end of file diff --git a/tools/robot/basicRobotInstall.sh b/tools/robot/basicRobotInstall.sh index 511821f..ba66010 100644 --- a/tools/robot/basicRobotInstall.sh +++ b/tools/robot/basicRobotInstall.sh @@ -2,6 +2,13 @@ echo "Installing basic software related with robotFramework" source /opt/venv/bin/activate; pip install --upgrade pip -pip install --upgrade robotframework; -pip install -r $1 +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash +source /root/.bashrc +nvm install node +yes|npx playwright install-deps +pip install -r $1 +rfbrowser clean-node +rfbrowser init --skip-browsers +npx playwright install +npx playwright install-deps echo "Robot framework installed" -- GitLab From 704b6b79b35c7a4ae523adc57cccc1fcf92a344f Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Wed, 15 May 2024 13:23:08 +0200 Subject: [PATCH 03/26] Add new object at loggin to send redis message to events service --- .../capif_events/core/consumer_messager.py | 9 ++- .../capif_events/core/internal_event_ops.py | 4 +- .../capif_events/core/notifications.py | 47 ++++++++++++ .../TS29222_CAPIF_Events_API/requirements.txt | 2 + .../core/invocationlogs.py | 23 ++++-- .../api_invocation_logs/core/publisher.py | 2 +- .../api_invocation_logs/core/redis_event.py | 22 ++++++ .../CAPIF Api Events/capif_events_api.robot | 17 ++++- tests/libraries/api_events/bodyRequests.py | 75 +++++++++++-------- .../api_logging_service/bodyRequests.py | 6 +- 10 files changed, 164 insertions(+), 43 deletions(-) create mode 100644 services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py diff --git a/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py b/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py index 0f9fa6a..fa38290 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py @@ -16,13 +16,20 @@ class Subscriber(): self.notification = Notifications() self.event_ops = InternalEventOperations() self.p = self.r.pubsub() - self.p.subscribe("events", "internal-messages") + self.p.subscribe("events", "internal-messages", "events-log") def listen(self): for raw_message in self.p.listen(): + current_app.logger.info(raw_message) if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "events": current_app.logger.info("Event received") self.notification.send_notifications(raw_message["data"].decode('utf-8')) + if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "events-log": + current_app.logger.info("Event-log received") + event_redis=json.loads(raw_message["data"].decode('utf-8')) + current_app.logger.info(json.dumps(event_redis, indent=4)) + self.notification.send_notifications_new(event_redis) + elif raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": message, *invoker_id = raw_message["data"].decode('utf-8').split(":") diff --git a/services/TS29222_CAPIF_Events_API/capif_events/core/internal_event_ops.py b/services/TS29222_CAPIF_Events_API/capif_events/core/internal_event_ops.py index 3f4fc6a..437f7e3 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/internal_event_ops.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/internal_event_ops.py @@ -20,10 +20,10 @@ class InternalEventOperations(Resource): #self.auth_manager.remove_auth_all_event(subscriber_id) def get_event_subscriptions(self, event): + current_app.logger.info("get subscription from db") try: mycol = self.db.get_col_by_name(self.db.event_collection) - - query= {'events':event} + query={'events':{'$in':[event]}} subscriptions = mycol.find(query) if subscriptions is None: 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 a02f0ab..205d145 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py @@ -8,6 +8,8 @@ from ..encoder import JSONEncoder import sys import json from flask import current_app +import asyncio +import aiohttp class Notifications(): @@ -37,7 +39,52 @@ class Notifications(): current_app.logger.error("An exception occurred ::" + str(e)) return False + def send_notifications_new(self, redis_event): + try: + if redis_event.get('event', None) == None: + raise("Event value is not present on received event from REDIS") + + current_app.logger.info("Received event " + redis_event.get('event') + ", sending notifications") + subscriptions = self.events_ops.get_event_subscriptions(redis_event.get('event')) + # message, *ids = event.split(":") + current_app.logger.info(subscriptions) + + for sub in subscriptions: + url = sub["notification_destination"] + current_app.logger.debug(url) + event_detail=None + if redis_event.get('key', None) != None and redis_event.get('information', None) != None: + # current_app.logger.debug(json.dumps(redis_event.get('information'),cls=JSONEncoder)) + # event_detail=CAPIFEventDetail().from_dict({redis_event.get('key'):redis_event.get('information')}) + # current_app.logger.debug(json.dumps(event_detail,cls=JSONEncoder)) + event_detail={redis_event.get('key'):redis_event.get('information')} + + data = EventNotification(sub["subscription_id"], events=redis_event.get('event'), event_detail=[event_detail]) + current_app.logger.debug(json.dumps(data,cls=JSONEncoder)) + + # self.request_post(url, data) + asyncio.run(self.send(url, json.dumps(data,cls=JSONEncoder))) + + except Exception as e: + current_app.logger.error("An exception occurred ::" + str(e)) + return False + def request_post(self, url, data): headers = {'content-type': 'application/json'} return requests.post(url, json={'text': str(data.to_str())}, headers=headers) + + async def send_request(self, url, data): + async with aiohttp.ClientSession() as session: + timeout = aiohttp.ClientTimeout(total=10) # Establecer timeout a 10 segundos + async with session.post(url, json=data, timeout=timeout) as response: + return await response.text() + + async def send(self, url, data): + try: + response = await self.send_request(url, data) + print(response) + except asyncio.TimeoutError: + print("Timeout: Request timeout") + + diff --git a/services/TS29222_CAPIF_Events_API/requirements.txt b/services/TS29222_CAPIF_Events_API/requirements.txt index bbef950..743f814 100644 --- a/services/TS29222_CAPIF_Events_API/requirements.txt +++ b/services/TS29222_CAPIF_Events_API/requirements.txt @@ -21,3 +21,5 @@ rfc3987 redis flask_executor Flask-APScheduler +aiohttp==3.9.5 +async-timeout==4.0.3 diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py index aab8309..5a11c59 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py @@ -13,9 +13,11 @@ from ..util import dict_to_camel_case, clean_empty from .resources import Resource from .responses import bad_request_error, internal_server_error, forbidden_error, not_found_error, unauthorized_error, make_response from ..models.invocation_log import InvocationLog -from .publisher import Publisher +# from .publisher import Publisher +from .redis_event import RedisEvent +import copy -publisher_ops = Publisher() +# publisher_ops = Publisher() class LoggingInvocationOperations(Resource): @@ -64,7 +66,7 @@ class LoggingInvocationOperations(Resource): return None def add_invocationlog(self, aef_id, invocationlog): - + mycol = self.db.get_col_by_name(self.db.invocation_logs) try: @@ -82,17 +84,28 @@ class LoggingInvocationOperations(Resource): return result current_app.logger.debug("Check service apis") + event=None + invocation_log_base=json.loads(json.dumps(invocationlog, cls=JSONEncoder)) + for log in invocationlog.logs: result = self.__check_service_apis(log.api_id, log.api_name) + current_app.logger.debug("Inside for loop.") if result is not None: return result if log.result: + current_app.logger.debug(log) if int(log.result) >= 200 and int(log.result) < 300: - publisher_ops.publish_message("events", "SERVICE_API_INVOCATION_SUCCESS") + event="SERVICE_API_INVOCATION_SUCCESS" else: - publisher_ops.publish_message("events", "SERVICE_API_INVOCATION_FAILURE") + event="SERVICE_API_INVOCATION_FAILURE" + + current_app.logger.info(event) + invocation_log_base['logs']=[log] + RedisEvent(event,invocation_log_base,"invocationLogs").send_event() + + current_app.logger.debug("After log check") current_app.logger.debug("Check existing logs") my_query = {'aef_id': aef_id, 'api_invoker_id': invocationlog.api_invoker_id} diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/publisher.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/publisher.py index 3898c4b..a15c0d9 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/publisher.py +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/publisher.py @@ -5,7 +5,7 @@ from flask import current_app class Publisher(): def __init__(self): - self. r = redis.Redis(host='redis', port=6379, db=0) + self.r = redis.Redis(host='redis', port=6379, db=0) def publish_message(self, channel, message): self.r.publish(channel, message) diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py new file mode 100644 index 0000000..3b80a45 --- /dev/null +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py @@ -0,0 +1,22 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + +class RedisEvent(): + def __init__(self, event, information, event_detail_key="invocationLogs") -> None: + self.redis_event={ + "event": event, + "key": event_detail_key, + "information":information + } + + def to_string(self): + return json.dumps(self.redis_event, cls=JSONEncoder) + + def send_event(self): + publisher_ops.publish_message("events-log",self.to_string()) + + def __call__(self): + return self.redis_event \ No newline at end of file diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index aceeb1b..4bff370 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -139,6 +139,7 @@ Deletes an individual CAPIF Event Subscription with invalid SubscriptionId ... detail=User not authorized ... cause=You are not the owner of this resource + Prueba JMS [Tags] jms-1 # Log "Prueba 1" @@ -163,13 +164,27 @@ Prueba JMS ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + # Subscribe to events + ${events_list}= Create List SERVICE_API_INVOCATION_SUCCESS SERVICE_API_INVOCATION_FAILURE + ${request_body}= Create Events Subscription events=@{events_list} notificationDestination=http://127.0.0.1:9090/ + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + # Create Log Entry ${request_body}= Create Log Entry ... ${register_user_info['aef_id']} ... ${register_user_info_invoker['api_invoker_id']} ... ${api_ids} ... ${api_names} - ... '200' + ... 200 ${resp}= Post Request Capif ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs ... json=${request_body} diff --git a/tests/libraries/api_events/bodyRequests.py b/tests/libraries/api_events/bodyRequests.py index ab6e4da..3d1de26 100644 --- a/tests/libraries/api_events/bodyRequests.py +++ b/tests/libraries/api_events/bodyRequests.py @@ -1,32 +1,47 @@ -def create_events_subscription(): +def create_events_subscription(events=["SERVICE_API_AVAILABLE", "API_INVOKER_ONBOARDED"],notificationDestination="http://robot.testing", eventFilters=None, eventReq=None, requestTestNotification=None, supportedFeatures=None,websockNotifConfig=None): + event_subscription={ + "events": events, + "notificationDestination": notificationDestination, + } + if eventFilters != None: + event_subscription['eventFilters']=eventFilters + if eventReq != None: + event_subscription['eventReq']=eventReq + if requestTestNotification != None: + event_subscription['requestTestNotification']=requestTestNotification + if supportedFeatures != None: + event_subscription['supportedFeatures']=supportedFeatures + if websockNotifConfig != None: + event_subscription['websockNotifConfig']=websockNotifConfig + + return event_subscription + +def create_capif_event_filter(aefIds=None, apiIds=None, apiInvokerIds=None): + if aefIds == None and apiIds == None and apiInvokerIds: + raise("Error, no data present to create event filter") + capif_event_filter=dict() + if aefIds != None: + capif_event_filter['aefIds']=aefIds + if apiIds != None: + capif_event_filter['apiIds']=apiIds + if apiInvokerIds != None: + capif_event_filter['apiInvokerIds']=apiInvokerIds + return capif_event_filter + +def create_default_event_req(): + return { + "grpRepTime": 5, + "immRep": True, + "maxReportNbr": 0, + "monDur": "2000-01-23T04:56:07+00:00", + "partitionCriteria": ["TAC", "GEOAREA"], + "repPeriod": 6, + "sampRatio": 15 + } + +def create_websock_notif_config_default(): return { - "eventFilters": [ - { - "aefIds": ["aefIds", "aefIds"], - "apiIds": ["apiIds", "apiIds"], - "apiInvokerIds": ["apiInvokerIds", "apiInvokerIds"] - }, - { - "aefIds": ["aefIds", "aefIds"], - "apiIds": ["apiIds", "apiIds"], - "apiInvokerIds": ["apiInvokerIds", "apiInvokerIds"] - } - ], - "eventReq": { - "grpRepTime": 5, - "immRep": True, - "maxReportNbr": 0, - "monDur": "2000-01-23T04:56:07+00:00", - "partitionCriteria": ["TAC", "GEOAREA"], - "repPeriod": 6, - "sampRatio": 15 - }, - "events": ["SERVICE_API_AVAILABLE", "API_INVOKER_ONBOARDED"], - "notificationDestination": "http://robot.testing", - "requestTestNotification": True, - "supportedFeatures": "aaa", - "websockNotifConfig": { - "requestWebsocketUri": True, - "websocketUri": "websocketUri" - } + "requestWebsocketUri": True, + "websocketUri": "websocketUri" } + diff --git a/tests/libraries/api_logging_service/bodyRequests.py b/tests/libraries/api_logging_service/bodyRequests.py index fc2cad4..e0e70da 100644 --- a/tests/libraries/api_logging_service/bodyRequests.py +++ b/tests/libraries/api_logging_service/bodyRequests.py @@ -46,7 +46,7 @@ def create_log_entry(aefId, apiInvokerId, apiId, apiName, result='200'): "uri": "http://resource/endpoint", "protocol": "HTTP_1_1", "operation": "GET", - "result": "string", + "result": result, "invocationTime": "2023-03-30T10:30:21.408Z", "invocationLatency": 0, "inputParameters": "string", @@ -78,7 +78,7 @@ def create_log_entry(aefId, apiInvokerId, apiId, apiName, result='200'): } return data -def create_log_entry_bad_service(aefId, apiInvokerId): +def create_log_entry_bad_service(aefId, apiInvokerId, result=500): data = { "aefId": aefId, "apiInvokerId": apiInvokerId, @@ -91,7 +91,7 @@ def create_log_entry_bad_service(aefId, apiInvokerId): "uri": "string", "protocol": "HTTP_1_1", "operation": "GET", - "result": "string", + "result": result, "invocationTime": "2023-03-30T10:30:21.408Z", "invocationLatency": 0, "inputParameters": "string", -- GitLab From 83caa266e62a421f95fe9c66e3927de3c5479789 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Wed, 15 May 2024 17:32:09 +0200 Subject: [PATCH 04/26] mock server and changes on tests to select notification destination --- .../capif_events/core/notifications.py | 6 +- .../CAPIF Api Events/capif_events_api.robot | 2 +- tests/libraries/mock_server/mock_server.py | 55 +++++++++++++++++++ 3 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 tests/libraries/mock_server/mock_server.py 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 205d145..c6a6b87 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py @@ -69,9 +69,9 @@ class Notifications(): current_app.logger.error("An exception occurred ::" + str(e)) return False - def request_post(self, url, data): - headers = {'content-type': 'application/json'} - return requests.post(url, json={'text': str(data.to_str())}, headers=headers) + # def request_post(self, url, data): + # headers = {'content-type': 'application/json'} + # return requests.post(url, json={'text': str(data.to_str())}, headers=headers) async def send_request(self, url, data): async with aiohttp.ClientSession() as session: diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index 4bff370..9ee68ff 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -166,7 +166,7 @@ Prueba JMS # Subscribe to events ${events_list}= Create List SERVICE_API_INVOCATION_SUCCESS SERVICE_API_INVOCATION_FAILURE - ${request_body}= Create Events Subscription events=@{events_list} notificationDestination=http://127.0.0.1:9090/ + ${request_body}= Create Events Subscription events=@{events_list} notificationDestination=http://192.168.0.119:9090/testing ${resp}= Post Request Capif ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions ... json=${request_body} diff --git a/tests/libraries/mock_server/mock_server.py b/tests/libraries/mock_server/mock_server.py new file mode 100644 index 0000000..151f096 --- /dev/null +++ b/tests/libraries/mock_server/mock_server.py @@ -0,0 +1,55 @@ +from flask import Flask, request +import logging +from logging.handlers import RotatingFileHandler + + +app = Flask(__name__) + +# Lista para almacenar las solicitudes recibidas +requests_received = [] + +def verbose_formatter(): + return logging.Formatter( + '{"timestamp": "%(asctime)s", "level": "%(levelname)s", "logger": "%(name)s", "function": "%(funcName)s", "line": %(lineno)d, "message": %(message)s}', + datefmt='%d/%m/%Y %H:%M:%S' + ) + + +def configure_logging(app): + del app.logger.handlers[:] + loggers = [app.logger, ] + handlers = [] + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.DEBUG) + console_handler.setFormatter(verbose_formatter()) + file_handler = RotatingFileHandler(filename="mock_server.log", maxBytes=1024 * 1024 * 100, backupCount=20) + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(verbose_formatter()) + handlers.append(console_handler) + handlers.append(file_handler) + + + for l in loggers: + for handler in handlers: + l.addHandler(handler) + l.propagate = False + l.setLevel(logging.DEBUG) + +@app.route('/testing', methods=['POST', 'GET']) +def index(): + if request.method == 'POST': + app.logger.debug(request.json) + requests_received.append(request.json) + return 'Mock Server is running' + +@app.route('/requests_list', methods=['GET','DELETE']) +def requests_list(): + if request.method == 'DELETE': + requests_received.clear() + return requests_received + + +configure_logging(app) + +if __name__ == '__main__': + app.run(host='0.0.0.0',port=9090,debug=True) -- GitLab From a7d30c852a9c9476577de567cc19725201a6d5c0 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Mon, 20 May 2024 16:12:17 +0200 Subject: [PATCH 05/26] New test template for SERVICE_API_INVOCATION_SUCCESS and FAILURE --- .gitignore | 3 +- .../capif_events/core/notifications.py | 7 +- services/clean_capif_docker_services.sh | 2 + .../CAPIF Api Events/capif_events_api.robot | 191 +++++++++++++++++- .../api_logging_service/bodyRequests.py | 114 ++++------- tests/libraries/common/bodyRequests.py | 2 +- tests/libraries/common/types.json | 61 ++++++ tests/libraries/mock_server/mock_server.py | 1 + tests/libraries/mock_server/requirements.txt | 1 + tests/requirements.txt | 2 +- tests/resources/common.resource | 74 +++++++ tools/robot/basicRequirements.txt | 6 +- 12 files changed, 367 insertions(+), 97 deletions(-) create mode 100644 tests/libraries/mock_server/requirements.txt diff --git a/.gitignore b/.gitignore index 66e4e33..e19b88a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ __pycache__/ *.crt *.zip *.srl +*.log services/nginx/certs/sign_req_body.json services/easy_rsa/certs/pki services/easy_rsa/certs/*EasyRSA* @@ -35,4 +36,4 @@ docs/testing_with_postman/package-lock.json results helm/capif/*.lock -helm/capif/charts \ No newline at end of file +helm/capif/charts 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 c6a6b87..e81386b 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py @@ -59,11 +59,11 @@ class Notifications(): # current_app.logger.debug(json.dumps(event_detail,cls=JSONEncoder)) event_detail={redis_event.get('key'):redis_event.get('information')} - data = EventNotification(sub["subscription_id"], events=redis_event.get('event'), event_detail=[event_detail]) + data = EventNotification(sub["subscription_id"], events=redis_event.get('event'), event_detail=event_detail) current_app.logger.debug(json.dumps(data,cls=JSONEncoder)) # self.request_post(url, data) - asyncio.run(self.send(url, json.dumps(data,cls=JSONEncoder))) + asyncio.run(self.send(url, json.loads(json.dumps(data,cls=JSONEncoder)))) except Exception as e: current_app.logger.error("An exception occurred ::" + str(e)) @@ -76,7 +76,8 @@ class Notifications(): async def send_request(self, url, data): async with aiohttp.ClientSession() as session: timeout = aiohttp.ClientTimeout(total=10) # Establecer timeout a 10 segundos - async with session.post(url, json=data, timeout=timeout) as response: + headers = {'content-type': 'application/json'} + async with session.post(url, json=data, timeout=timeout, headers=headers) as response: return await response.text() async def send(self, url, data): diff --git a/services/clean_capif_docker_services.sh b/services/clean_capif_docker_services.sh index fb89497..1bc6d6d 100755 --- a/services/clean_capif_docker_services.sh +++ b/services/clean_capif_docker_services.sh @@ -74,4 +74,6 @@ done docker network rm capif-network +docker volume prune --all --force + echo "Clean complete." diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index 9ee68ff..c4b6085 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -5,7 +5,6 @@ Library XML Resource /opt/robot-tests/tests/resources/common/basicRequests.robot Resource ../../resources/common.resource - Suite Teardown Reset Testing Environment Test Setup Reset Testing Environment @@ -139,13 +138,12 @@ Deletes an individual CAPIF Event Subscription with invalid SubscriptionId ... detail=User not authorized ... cause=You are not the owner of this resource - Prueba JMS [Tags] jms-1 - # Log "Prueba 1" - # Wait For Request - # Create a log entry - # [Tags] capif_api_logging_service-1 + + # Start Mock server + Check Mock Server + Clean Mock Server # Register APF ${register_user_info}= Provider Default Registration @@ -165,8 +163,10 @@ Prueba JMS ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} # Subscribe to events - ${events_list}= Create List SERVICE_API_INVOCATION_SUCCESS SERVICE_API_INVOCATION_FAILURE - ${request_body}= Create Events Subscription events=@{events_list} notificationDestination=http://192.168.0.119:9090/testing + ${events_list}= Create List SERVICE_API_INVOCATION_SUCCESS SERVICE_API_INVOCATION_FAILURE + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=http://192.168.0.14:9090/testing ${resp}= Post Request Capif ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions ... json=${request_body} @@ -178,13 +178,122 @@ Prueba JMS Check Response Variable Type And Values ${resp} 201 EventSubscription ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + ${results}= Create List 200 400 + + # Create Log Entry + ${request_body}= Create Log Entry + ... ${register_user_info['aef_id']} + ... ${register_user_info_invoker['api_invoker_id']} + ... ${api_ids} + ... ${api_names} + ... results=${results} + ${resp}= Post Request Capif + ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 InvocationLog + ${resource_url}= Check Location Header ${resp} ${LOCATION_LOGGING_RESOURCE_REGEX} + + Sleep 3s + + # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. + ${resp}= Get Mock Server Messages + ${notification_events_on_mock_server}= Set Variable ${resp.json()} + # Check if message follow EventNotification definition. + Check Variable ${notification_events_on_mock_server} EventNotification + # Check if mock server receive 2 events. + Length Should Be ${notification_events_on_mock_server} 2 + + # Now we create the expected events received at notification server according to logs sent to loggin service in order to check if all are present. + ${invocation_log_base}= Copy Dictionary ${request_body} deepcopy=True + + # Store log array because each log will be notified in one Event Notification + ${invocation_log_logs}= Set Variable ${invocation_log_base['logs']} + # Remove logs array from invocationLog data + Remove From Dictionary ${invocation_log_base} logs + Length Should Be ${invocation_log_logs} 2 + + # Create 2 invocationLogs from initial invocationLog present on request + ${invocation_logs_1}= Copy Dictionary ${invocation_log_base} deepcopy=True + ${invocation_logs_2}= Copy Dictionary ${invocation_log_base} deepcopy=True + # Create a log array with only one component + ${log_1}= Create List ${invocation_log_logs[0]} + ${log_2}= Create List ${invocation_log_logs[1]} + # Setup logs array with previously created list + Set To Dictionary ${invocation_logs_1} logs=${log_1} + Set To Dictionary ${invocation_logs_2} logs=${log_2} + # Create event details for each log + ${event_details_1}= Create dictionary invocationLogs=${invocation_logs_1} + ${event_details_2}= Create dictionary invocationLogs=${invocation_logs_2} + # Create Event with Event Details from invocationLog + ${event_1}= Create Dictionary + ... subscriptionId=${subscription_id} + ... events=SERVICE_API_INVOCATION_SUCCESS + ... eventDetail=${event_details_1} + ${event_2}= Create Dictionary + ... subscriptionId=${subscription_id} + ... events=SERVICE_API_INVOCATION_FAILURE + ... eventDetail=${event_details_2} + # Check if created events follow EventNotification format defined by 3gpp + Check Variable ${event_1} EventNotification + Check Variable ${event_2} EventNotification + + List Should Contain Value ${notification_events_on_mock_server} ${event_1} + List Should Contain Value ${notification_events_on_mock_server} ${event_2} + +Prueba JMS + [Tags] jms-2 + + # Start Mock server + Check Mock Server + Clean Mock Server + + # Register APF + ${register_user_info}= Provider Default Registration + + # Publish one api + Publish Service Api ${register_user_info} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Subscribe to events + ${events_list}= Create List SERVICE_API_INVOCATION_SUCCESS SERVICE_API_INVOCATION_FAILURE + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=http://192.168.0.14:9090/testing + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + + ${results}= Create List 200 400 + # Create Log Entry ${request_body}= Create Log Entry ... ${register_user_info['aef_id']} ... ${register_user_info_invoker['api_invoker_id']} ... ${api_ids} ... ${api_names} - ... 200 + ... results=${results} ${resp}= Post Request Capif ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs ... json=${request_body} @@ -196,3 +305,67 @@ Prueba JMS Check Response Variable Type And Values ${resp} 201 InvocationLog ${resource_url}= Check Location Header ${resp} ${LOCATION_LOGGING_RESOURCE_REGEX} + Sleep 3s + + # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. + ${resp}= Get Mock Server Messages + ${notification_events_on_mock_server}= Set Variable ${resp.json()} + # Check if message follow EventNotification definition. + Check Variable ${notification_events_on_mock_server} EventNotification + + # Create check Events to ensure all notifications were received + ${check_events} ${check_events_length}= Create Events From invocationLogs + ... ${subscription_id} + ... ${request_body} + + # Number of events received must be equal than events expected + Length Should Be ${notification_events_on_mock_server} ${check_events_length} + # Check if events received are the same than expected + FOR ${event} IN @{check_events} + Log ${event} + List Should Contain Value ${notification_events_on_mock_server} ${event} + END + + +*** Keywords *** +Create Events From invocationLogs + [Arguments] ${subscription_id} ${invocation_log} + + ${events}= Create List + + # Now we create the expected events received at notification server according to logs sent to loggin service in order to check if all are present. + ${invocation_log_base}= Copy Dictionary ${invocation_log} deepcopy=True + # Store log array because each log will be notified in one Event Notification + ${invocation_log_logs}= Copy List ${invocation_log_base['logs']} + # Remove logs array from invocationLog data + Remove From Dictionary ${invocation_log_base} logs + + FOR ${log} IN @{invocation_log_logs} + Log Dictionary ${log} + ${invocation_logs}= Copy Dictionary ${invocation_log_base} deepcopy=True + + # Get Event Enum for this result + ${event_enum}= Set Variable + IF ${log['result']} >= 200 and ${log['result']} < 300 + ${event_enum}= Set Variable SERVICE_API_INVOCATION_SUCCESS + ELSE + ${event_enum}= Set Variable SERVICE_API_INVOCATION_FAILURE + END + # Create a log array with only one component + ${log_list}= Create List ${log} + # Setup logs array with previously created list + Set To Dictionary ${invocation_logs} logs=${log_list} + # Create event details for each log + ${event_details}= Create dictionary invocationLogs=${invocation_logs} + # Create Event with Event Details from invocationLog and also is appended to events array + ${event}= Create Dictionary + ... subscriptionId=${subscription_id} + ... events=${event_enum} + ... eventDetail=${event_details} + Check Variable ${event} EventNotification + Append To List ${events} ${event} + END + + Log List ${events} + ${events_length}= Get Length ${events} + RETURN ${events} ${events_length} diff --git a/tests/libraries/api_logging_service/bodyRequests.py b/tests/libraries/api_logging_service/bodyRequests.py index e0e70da..9ab8210 100644 --- a/tests/libraries/api_logging_service/bodyRequests.py +++ b/tests/libraries/api_logging_service/bodyRequests.py @@ -1,81 +1,13 @@ -def create_log_entry(aefId, apiInvokerId, apiId, apiName, result='200'): +def create_log_entry(aefId, apiInvokerId, apiId, apiName, results=['200']): data = { "aefId": aefId, "apiInvokerId": apiInvokerId, - "logs": [ - { - "apiId": apiId[0], - "apiName": apiName[0], - "apiVersion": "v1", - "resourceName": "string", - "uri": "http://resource/endpoint", - "protocol": "HTTP_1_1", - "operation": "GET", - "result": result, - "invocationTime": "2023-03-30T10:30:21.408Z", - "invocationLatency": 0, - "inputParameters": "string", - "outputParameters": "string", - "srcInterface": { - "ipv4Addr": "192.168.1.1", - "fqdn": "string", - "port": 65535, - "apiPrefix": "string", - "securityMethods": [ - "PSK", - "string" - ] - }, - "destInterface": { - "ipv4Addr": "192.168.1.23", - "fqdn": "string", - "port": 65535, - "apiPrefix": "string", - "securityMethods": [ - "PSK", - "string" - ] - }, - "fwdInterface": "string" - }, - { - "apiId": apiId[0], - "apiName": apiName[0], - "apiVersion": "v2", - "resourceName": "string", - "uri": "http://resource/endpoint", - "protocol": "HTTP_1_1", - "operation": "GET", - "result": result, - "invocationTime": "2023-03-30T10:30:21.408Z", - "invocationLatency": 0, - "inputParameters": "string", - "outputParameters": "string", - "srcInterface": { - "ipv4Addr": "192.168.1.1", - "fqdn": "string", - "port": 65535, - "apiPrefix": "string", - "securityMethods": [ - "PSK", - "string" - ] - }, - "destInterface": { - "ipv4Addr": "192.168.1.23", - "fqdn": "string", - "port": 65535, - "apiPrefix": "string", - "securityMethods": [ - "PSK", - "string" - ] - }, - "fwdInterface": "string" - } - ], + "logs": [], "supportedFeatures": "ffff" } + if len(results) > 0: + for result in results: + data['logs'].append(create_log(apiId,apiName,result)) return data def create_log_entry_bad_service(aefId, apiInvokerId, result=500): @@ -92,28 +24,24 @@ def create_log_entry_bad_service(aefId, apiInvokerId, result=500): "protocol": "HTTP_1_1", "operation": "GET", "result": result, - "invocationTime": "2023-03-30T10:30:21.408Z", + "invocationTime": "2023-03-30T10:30:21.408000+00:00", "invocationLatency": 0, "inputParameters": "string", "outputParameters": "string", "srcInterface": { "ipv4Addr": "192.168.1.1", - "fqdn": "string", "port": 65535, - "apiPrefix": "string", "securityMethods": [ "PSK", - "string" + "PKI" ] }, "destInterface": { "ipv4Addr": "192.168.1.23", - "fqdn": "string", "port": 65535, - "apiPrefix": "string", "securityMethods": [ "PSK", - "string" + "PKI" ] }, "fwdInterface": "string" @@ -131,3 +59,29 @@ def get_api_ids_and_names_from_discover_response(discover_response): api_ids.append(service_api_description['apiId']) api_names.append(service_api_description['apiName']) return api_ids, api_names + + +def create_log(apiId, apiName, result): + log= { + "apiId": apiId[0], + "apiName": apiName[0], + "apiVersion": "v1", + "resourceName": "string", + "uri": "http://resource/endpoint", + "protocol": "HTTP_1_1", + "operation": "GET", + "result": result, + "invocationTime": "2023-03-30T10:30:21.408000+00:00", + "invocationLatency": 0, + "inputParameters": "string", + "outputParameters": "string", + "srcInterface": { + "ipv4Addr": "192.168.1.1", + "port": 65535, + "securityMethods": [ + "PSK", + "PKI" + ] + } + } + return log \ No newline at end of file diff --git a/tests/libraries/common/bodyRequests.py b/tests/libraries/common/bodyRequests.py index 3f1e482..1d4ec14 100644 --- a/tests/libraries/common/bodyRequests.py +++ b/tests/libraries/common/bodyRequests.py @@ -15,7 +15,7 @@ def check_variable(input, data_type): if isinstance(input, list): for one in input: check_variable(one, data_type) - return True + return True if data_type == "string": if isinstance(input, str): return True diff --git a/tests/libraries/common/types.json b/tests/libraries/common/types.json index 4b68359..220dd0f 100644 --- a/tests/libraries/common/types.json +++ b/tests/libraries/common/types.json @@ -259,6 +259,67 @@ "aefIds": "string" } }, + "EventNotification": { + "mandatory_attributes": { + "subscriptionId": "string", + "events": "CAPIFEvent" + }, + "optional_attributes": { + "eventDetail": "CAPIFEventDetail" + } + }, + "CAPIFEventDetail": { + "mandatory_attributes": {}, + "optional_attributes": { + "serviceAPIDescriptions": "ServiceAPIDescription", + "apiIds": "string", + "apiInvokerIds": "string", + "accCtrlPolList": "AccessControlPolicyListExt", + "invocationLogs": "InvocationLog", + "apiTopoHide": "TopologyHiding" + } + }, + "AccessControlPolicyListExt":{ + "mandatory_attributes": { + "apiId": "string" + }, + "optional_attributes": { + "apiInvokerPolicies": "ApiInvokerPolicy" + } + }, + "TopologyHiding": { + "mandatory_attributes":{ + "apiId": "string", + "routingRules": "RoutingRule" + }, + "optional_attributes": {} + }, + "RoutingRule":{ + "mandatory_attributes": { + "aefProfile": "AefProfile" + }, + "optional_attributes": { + "ipv4AddrRanges": "Ipv4AddressRange", + "ipv6AddrRanges": "Ipv6AddressRange" + } + }, + "Ipv4AddressRange":{ + "mandatory_attributes": {}, + "optional_attributes": { + "start": "Ipv4Addr", + "stop": "Ipv4Addr" + } + }, + "Ipv4Addr": { + "regex": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$" + }, + "Ipv6AddressRange":{ + "mandatory_attributes":{ + "start": "string", + "end": "string" + }, + "optional_attributes":{} + }, "ReportingInformation": { "mandatory_attributes": {}, "optional_attributes": { diff --git a/tests/libraries/mock_server/mock_server.py b/tests/libraries/mock_server/mock_server.py index 151f096..18d3f92 100644 --- a/tests/libraries/mock_server/mock_server.py +++ b/tests/libraries/mock_server/mock_server.py @@ -39,6 +39,7 @@ def configure_logging(app): def index(): if request.method == 'POST': app.logger.debug(request.json) + app.logger.debug(request.headers) requests_received.append(request.json) return 'Mock Server is running' diff --git a/tests/libraries/mock_server/requirements.txt b/tests/libraries/mock_server/requirements.txt new file mode 100644 index 0000000..abf2862 --- /dev/null +++ b/tests/libraries/mock_server/requirements.txt @@ -0,0 +1 @@ +flask==3.0.3 \ No newline at end of file diff --git a/tests/requirements.txt b/tests/requirements.txt index b983217..3119b5c 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,2 +1,2 @@ # Requirements file for tests. -robotframework-archivelibrary == 0.4.2 + diff --git a/tests/resources/common.resource b/tests/resources/common.resource index c46131b..4f8136e 100644 --- a/tests/resources/common.resource +++ b/tests/resources/common.resource @@ -1,5 +1,7 @@ *** Settings *** Library /opt/robot-tests/tests/libraries/helpers.py +Library Process +Library Collections Variables /opt/robot-tests/tests/libraries/environment.py Resource /opt/robot-tests/tests/resources/common/basicRequests.robot @@ -30,6 +32,9 @@ ${CAPIF_CALLBACK_PORT} 8086 ${REGISTER_ADMIN_USER} admin ${REGISTER_ADMIN_PASSWORD} password123 +${MOCK_SERVER_URL} http://192.168.0.14:9090 + + ${DISCOVER_URL} /service-apis/v1/allServiceAPIs?api-invoker-id= @@ -85,3 +90,72 @@ Remove Keys From Object Test ${TEST NAME} Currently Not Supported Log Test "${TEST NAME}" Currently not supported WARN Skip Test "${TEST NAME}" Currently not supported + +Start Mock server + Log Starting mock Server for Robot Framework. + + # Run Process pip3 install -r /opt/robot-tests/tests/libraries/mock_server/requirements.txt + ${server_process}= Start Process python3 /opt/robot-tests/tests/libraries/mock_server/mock_server.py + Log PID: ${server_process.pid} + + Create Session mockserver ${MOCK_SERVER_URL} + + ${endpoint}= Set Variable /testing + + ${json}= Create Dictionary events="SERVICE_API_INVOCATION_SUCCESS" subscriptionId="255545008cd3937f5554a3651bbfb9" + + ${resp}= POST On Session + ... mockserver + ... ${endpoint} + ... json=${json} + ... expected_status=any + ... verify=False + + ${result}= Terminate Process ${server_process} + +Check Mock Server + Log Checking mock Server for Robot Framework. + + Create Session mockserver ${MOCK_SERVER_URL} + + ${endpoint}= Set Variable /requests_list + + ${resp}= GET On Session + ... mockserver + ... ${endpoint} + ... expected_status=any + ... verify=False + + Status Should Be 200 ${resp} + +Clean Mock Server + Log Checking mock Server for Robot Framework. + + Create Session mockserver ${MOCK_SERVER_URL} + + ${endpoint}= Set Variable /requests_list + + ${resp}= DELETE On Session + ... mockserver + ... ${endpoint} + ... expected_status=any + ... verify=False + + Status Should Be 200 ${resp} + + +Get Mock Server Messages + Log Checking mock Server for Robot Framework. + + Create Session mockserver ${MOCK_SERVER_URL} + + ${endpoint}= Set Variable /requests_list + + ${resp}= GET On Session + ... mockserver + ... ${endpoint} + ... expected_status=any + ... verify=False + + Status Should Be 200 ${resp} + RETURN ${resp} \ No newline at end of file diff --git a/tools/robot/basicRequirements.txt b/tools/robot/basicRequirements.txt index 99eafd7..282a034 100644 --- a/tools/robot/basicRequirements.txt +++ b/tools/robot/basicRequirements.txt @@ -25,6 +25,7 @@ docutils==0.19 exceptiongroup==1.0.0rc9 filelock==3.8.0 flake8==3.9.2 +flask==3.0.3 h11==0.14.0 idna==3.4 iniconfig==1.1.1 @@ -69,6 +70,7 @@ rellu==0.7 requests==2.28.1 rfc3987==1.3.8 robotframework==7.0 +robotframework-archivelibrary == 0.4.2 robotframework-browser==18.3.0 robotframework-httpctrl==0.3.1 robotframework-lint==1.1 @@ -95,8 +97,8 @@ trio-websocket==0.9.2 typing-extensions==4.11.0 urllib3==1.26.12 virtualenv==20.16.5 -watchdog==0.9.0 +watchdog==4.0.0 webdrivermanager==0.10.0 wrapt==1.15.0 wsproto==1.2.0 -xlrd==2.0.1 \ No newline at end of file +xlrd==2.0.1 -- GitLab From fe548e6ed474b6d148ff3a65c82ddd8accb7aaf9 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Mon, 20 May 2024 16:34:26 +0200 Subject: [PATCH 06/26] Create script to run robot mock server --- .../CAPIF Api Events/capif_events_api.robot | 44 ------------------- tests/libraries/mock_server/mock_server.py | 4 +- .../libraries/mock_server/run_mock_server.sh | 40 +++++++++++++++++ tests/resources/common/basicRequests.robot | 43 ++++++++++++++++++ 4 files changed, 85 insertions(+), 46 deletions(-) create mode 100755 tests/libraries/mock_server/run_mock_server.sh diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index c4b6085..c1ea4c7 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -325,47 +325,3 @@ Prueba JMS Log ${event} List Should Contain Value ${notification_events_on_mock_server} ${event} END - - -*** Keywords *** -Create Events From invocationLogs - [Arguments] ${subscription_id} ${invocation_log} - - ${events}= Create List - - # Now we create the expected events received at notification server according to logs sent to loggin service in order to check if all are present. - ${invocation_log_base}= Copy Dictionary ${invocation_log} deepcopy=True - # Store log array because each log will be notified in one Event Notification - ${invocation_log_logs}= Copy List ${invocation_log_base['logs']} - # Remove logs array from invocationLog data - Remove From Dictionary ${invocation_log_base} logs - - FOR ${log} IN @{invocation_log_logs} - Log Dictionary ${log} - ${invocation_logs}= Copy Dictionary ${invocation_log_base} deepcopy=True - - # Get Event Enum for this result - ${event_enum}= Set Variable - IF ${log['result']} >= 200 and ${log['result']} < 300 - ${event_enum}= Set Variable SERVICE_API_INVOCATION_SUCCESS - ELSE - ${event_enum}= Set Variable SERVICE_API_INVOCATION_FAILURE - END - # Create a log array with only one component - ${log_list}= Create List ${log} - # Setup logs array with previously created list - Set To Dictionary ${invocation_logs} logs=${log_list} - # Create event details for each log - ${event_details}= Create dictionary invocationLogs=${invocation_logs} - # Create Event with Event Details from invocationLog and also is appended to events array - ${event}= Create Dictionary - ... subscriptionId=${subscription_id} - ... events=${event_enum} - ... eventDetail=${event_details} - Check Variable ${event} EventNotification - Append To List ${events} ${event} - END - - Log List ${events} - ${events_length}= Get Length ${events} - RETURN ${events} ${events_length} diff --git a/tests/libraries/mock_server/mock_server.py b/tests/libraries/mock_server/mock_server.py index 18d3f92..e45fae0 100644 --- a/tests/libraries/mock_server/mock_server.py +++ b/tests/libraries/mock_server/mock_server.py @@ -1,7 +1,7 @@ from flask import Flask, request import logging from logging.handlers import RotatingFileHandler - +import os app = Flask(__name__) @@ -53,4 +53,4 @@ def requests_list(): configure_logging(app) if __name__ == '__main__': - app.run(host='0.0.0.0',port=9090,debug=True) + app.run(host=os.environ.get("IP",'0.0.0.0'),port=os.environ.get("PORT",9090),debug=True) diff --git a/tests/libraries/mock_server/run_mock_server.sh b/tests/libraries/mock_server/run_mock_server.sh new file mode 100755 index 0000000..10c4ba2 --- /dev/null +++ b/tests/libraries/mock_server/run_mock_server.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +help() { + echo "Usage: $1 " + echo " -i : Setup different host ip for mock server (default 0.0.0.0)" + echo " -p : Setup different port for mock server (default 9090)" + echo " -h : show this help" + exit 1 +} + +IP=0.0.0.0 +PORT=9090 + +# Read params +while getopts ":i:p:h" opt; do + case $opt in + i) + IP="$OPTARG" + ;; + p) + PORT=$OPTARG + ;; + h) + help + ;; + \?) + echo "Not valid option: -$OPTARG" >&2 + help + ;; + :) + echo "The -$OPTARG option requires an argument." >&2 + help + ;; + esac +done + +echo Robot Framework Mock Server will listen on $IP:$PORT +pip install -r requirements.txt + +IP=$IP PORT=$PORT python mock_server.py diff --git a/tests/resources/common/basicRequests.robot b/tests/resources/common/basicRequests.robot index ea3a96f..5872a57 100644 --- a/tests/resources/common/basicRequests.robot +++ b/tests/resources/common/basicRequests.robot @@ -744,3 +744,46 @@ Create Security Context Between invoker and provider ... username=${register_user_info_invoker['management_cert']} Check Response Variable Type And Values ${resp} 201 ServiceSecurity + + +Create Events From invocationLogs + [Arguments] ${subscription_id} ${invocation_log} + + ${events}= Create List + + # Now we create the expected events received at notification server according to logs sent to loggin service in order to check if all are present. + ${invocation_log_base}= Copy Dictionary ${invocation_log} deepcopy=True + # Store log array because each log will be notified in one Event Notification + ${invocation_log_logs}= Copy List ${invocation_log_base['logs']} + # Remove logs array from invocationLog data + Remove From Dictionary ${invocation_log_base} logs + + FOR ${log} IN @{invocation_log_logs} + Log Dictionary ${log} + ${invocation_logs}= Copy Dictionary ${invocation_log_base} deepcopy=True + + # Get Event Enum for this result + ${event_enum}= Set Variable + IF ${log['result']} >= 200 and ${log['result']} < 300 + ${event_enum}= Set Variable SERVICE_API_INVOCATION_SUCCESS + ELSE + ${event_enum}= Set Variable SERVICE_API_INVOCATION_FAILURE + END + # Create a log array with only one component + ${log_list}= Create List ${log} + # Setup logs array with previously created list + Set To Dictionary ${invocation_logs} logs=${log_list} + # Create event details for each log + ${event_details}= Create dictionary invocationLogs=${invocation_logs} + # Create Event with Event Details from invocationLog and also is appended to events array + ${event}= Create Dictionary + ... subscriptionId=${subscription_id} + ... events=${event_enum} + ... eventDetail=${event_details} + Check Variable ${event} EventNotification + Append To List ${events} ${event} + END + + Log List ${events} + ${events_length}= Get Length ${events} + RETURN ${events} ${events_length} -- GitLab From c75dc334fa086dd4f3d2a6aa697cd62d56761c93 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Mon, 20 May 2024 17:21:50 +0200 Subject: [PATCH 07/26] Fix previous tests --- .../capif_events/core/notifications.py | 22 ++++--------------- .../api_logging_service/bodyRequests.py | 15 ++++++++----- 2 files changed, 14 insertions(+), 23 deletions(-) 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 e81386b..e7b7dd5 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py @@ -19,21 +19,12 @@ class Notifications(): def send_notifications(self, event): current_app.logger.info("Received event, sending notifications") subscriptions = self.events_ops.get_event_subscriptions(event) - # message, *ids = event.split(":") try: for sub in subscriptions: url = sub["notification_destination"] data = EventNotification(sub["subscription_id"], events=event) - # details = CAPIFEventDetail() - # if message == "ACCESS_CONTROL_POLICY_UPDATE": - # current_app.logger.info("event: ACCESS_CONTROL_POLICY_UPDATE") - # acls = self.events_ops.get_acls(ids[0]) - # details.acc_ctrl_pol_list = AccessControlPolicyListExt(api_id=acls['service_id'], api_invoker_policies=acls['apiInvokerPolicies']) - - # data.event_detail=details self.request_post(url, data) - #current_app.logger.info("notification sended") except Exception as e: current_app.logger.error("An exception occurred ::" + str(e)) @@ -46,7 +37,6 @@ class Notifications(): current_app.logger.info("Received event " + redis_event.get('event') + ", sending notifications") subscriptions = self.events_ops.get_event_subscriptions(redis_event.get('event')) - # message, *ids = event.split(":") current_app.logger.info(subscriptions) for sub in subscriptions: @@ -54,24 +44,20 @@ class Notifications(): current_app.logger.debug(url) event_detail=None if redis_event.get('key', None) != None and redis_event.get('information', None) != None: - # current_app.logger.debug(json.dumps(redis_event.get('information'),cls=JSONEncoder)) - # event_detail=CAPIFEventDetail().from_dict({redis_event.get('key'):redis_event.get('information')}) - # current_app.logger.debug(json.dumps(event_detail,cls=JSONEncoder)) event_detail={redis_event.get('key'):redis_event.get('information')} data = EventNotification(sub["subscription_id"], events=redis_event.get('event'), event_detail=event_detail) current_app.logger.debug(json.dumps(data,cls=JSONEncoder)) - - # self.request_post(url, data) + asyncio.run(self.send(url, json.loads(json.dumps(data,cls=JSONEncoder)))) except Exception as e: current_app.logger.error("An exception occurred ::" + str(e)) return False - # def request_post(self, url, data): - # headers = {'content-type': 'application/json'} - # return requests.post(url, json={'text': str(data.to_str())}, headers=headers) + def request_post(self, url, data): + headers = {'content-type': 'application/json'} + return requests.post(url, json={'text': str(data.to_str())}, headers=headers) async def send_request(self, url, data): async with aiohttp.ClientSession() as session: diff --git a/tests/libraries/api_logging_service/bodyRequests.py b/tests/libraries/api_logging_service/bodyRequests.py index 9ab8210..a4d2a18 100644 --- a/tests/libraries/api_logging_service/bodyRequests.py +++ b/tests/libraries/api_logging_service/bodyRequests.py @@ -1,4 +1,4 @@ -def create_log_entry(aefId, apiInvokerId, apiId, apiName, results=['200']): +def create_log_entry(aefId, apiInvokerId, apiId, apiName, results=['200','500'],api_versions=['v1','v2']): data = { "aefId": aefId, "apiInvokerId": apiInvokerId, @@ -6,11 +6,16 @@ def create_log_entry(aefId, apiInvokerId, apiId, apiName, results=['200']): "supportedFeatures": "ffff" } if len(results) > 0: + count=0 for result in results: - data['logs'].append(create_log(apiId,apiName,result)) + data['logs'].append(create_log(apiId,apiName,result,api_versions[count])) + count=count+1 + if count == len(api_versions): + count=0 + return data -def create_log_entry_bad_service(aefId, apiInvokerId, result=500): +def create_log_entry_bad_service(aefId, apiInvokerId, result='500'): data = { "aefId": aefId, "apiInvokerId": apiInvokerId, @@ -61,11 +66,11 @@ def get_api_ids_and_names_from_discover_response(discover_response): return api_ids, api_names -def create_log(apiId, apiName, result): +def create_log(apiId, apiName, result, api_version='v1'): log= { "apiId": apiId[0], "apiName": apiName[0], - "apiVersion": "v1", + "apiVersion": api_version, "resourceName": "string", "uri": "http://resource/endpoint", "protocol": "HTTP_1_1", -- GitLab From 084ceee2ccb958c3616bf137df537f9e5ccd77a5 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Mon, 20 May 2024 17:37:15 +0200 Subject: [PATCH 08/26] Setup robot script mockserver --- .../api_invocation_logs/core/redis_event.py | 2 +- services/run_capif_tests.sh | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py index 3b80a45..d42a1ef 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py @@ -5,7 +5,7 @@ import json publisher_ops = Publisher() class RedisEvent(): - def __init__(self, event, information, event_detail_key="invocationLogs") -> None: + def __init__(self, event, information, event_detail_key) -> None: self.redis_event={ "event": event, "key": event_detail_key, diff --git a/services/run_capif_tests.sh b/services/run_capif_tests.sh index b73893a..f294bac 100755 --- a/services/run_capif_tests.sh +++ b/services/run_capif_tests.sh @@ -12,6 +12,7 @@ ROBOT_DOCKER_FILE_FOLDER=$REPOSITORY_BASE_FOLDER/tools/robot CAPIF_HOSTNAME=capifcore CAPIF_HTTP_PORT=8080 CAPIF_HTTPS_PORT=443 +MOCK_SERVER_URL=http://mockserver:9090 echo "HOSTNAME = $CAPIF_HOSTNAME" echo "CAPIF_HTTP_PORT = $CAPIF_HTTP_PORT" @@ -45,8 +46,10 @@ docker run -ti --rm --network="host" \ --add-host host.docker.internal:host-gateway \ --add-host vault:host-gateway \ --add-host register:host-gateway \ + --add-host mockserver:host-gateway \ -v $TEST_FOLDER:/opt/robot-tests/tests \ -v $RESULT_FOLDER:/opt/robot-tests/results ${DOCKER_ROBOT_IMAGE}:${DOCKER_ROBOT_IMAGE_VERSION} \ --variable CAPIF_HOSTNAME:$CAPIF_HOSTNAME \ --variable CAPIF_HTTP_PORT:$CAPIF_HTTP_PORT \ - --variable CAPIF_HTTPS_PORT:$CAPIF_HTTPS_PORT $@ + --variable CAPIF_HTTPS_PORT:$CAPIF_HTTPS_PORT \ + --variable MOCK_SERVER_URL:$MOCK_SERVER_URL $@ -- GitLab From 11227f06d7f5625c132fd4cdd4f82f039048194d Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Wed, 22 May 2024 10:51:19 +0200 Subject: [PATCH 09/26] improvement of scripts --- services/run_capif_tests.sh | 26 ++++++++++++++++++- .../CAPIF Api Events/capif_events_api.robot | 4 +++ tests/resources/common.resource | 8 +++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/services/run_capif_tests.sh b/services/run_capif_tests.sh index f294bac..944e3b2 100755 --- a/services/run_capif_tests.sh +++ b/services/run_capif_tests.sh @@ -9,14 +9,33 @@ RESULT_FOLDER=$REPOSITORY_BASE_FOLDER/results ROBOT_DOCKER_FILE_FOLDER=$REPOSITORY_BASE_FOLDER/tools/robot # nginx Hostname and http port (80 by default) to reach for tests +# CAPIF_REGISTER=registercapif.mobilesandbox.cloud +CAPIF_REGISTER=capifcore +# CAPIF_REGISTER_PORT=37211 +CAPIF_REGISTER_PORT=8084 +# CAPIF_HOSTNAME=capif.mobilesandbox.cloud CAPIF_HOSTNAME=capifcore CAPIF_HTTP_PORT=8080 CAPIF_HTTPS_PORT=443 + +# VAULT access configuration +# CAPIF_VAULT=vault.5gnacar.int +CAPIF_VAULT=vault +CAPIF_VAULT_PORT=8200 +# CAPIF_VAULT_TOKEN=dev-only-token +CAPIF_VAULT_TOKEN=read-ca-token + MOCK_SERVER_URL=http://mockserver:9090 -echo "HOSTNAME = $CAPIF_HOSTNAME" + +echo "CAPIF_HOSTNAME = $CAPIF_HOSTNAME" +echo "CAPIF_REGISTER = $CAPIF_REGISTER" echo "CAPIF_HTTP_PORT = $CAPIF_HTTP_PORT" echo "CAPIF_HTTPS_PORT = $CAPIF_HTTPS_PORT" +echo "CAPIF_VAULT = $CAPIF_VAULT" +echo "CAPIF_VAULT_PORT = $CAPIF_VAULT_PORT" +echo "CAPIF_VAULT_TOKEN = $CAPIF_VAULT_TOKEN" +echo "MOCK_SERVER_URL = $MOCK_SERVER_URL" docker >/dev/null 2>/dev/null if [[ $? -ne 0 ]] @@ -52,4 +71,9 @@ docker run -ti --rm --network="host" \ --variable CAPIF_HOSTNAME:$CAPIF_HOSTNAME \ --variable CAPIF_HTTP_PORT:$CAPIF_HTTP_PORT \ --variable CAPIF_HTTPS_PORT:$CAPIF_HTTPS_PORT \ + --variable CAPIF_REGISTER:$CAPIF_REGISTER \ + --variable CAPIF_REGISTER_PORT:$CAPIF_REGISTER_PORT \ + --variable CAPIF_VAULT:$CAPIF_VAULT \ + --variable CAPIF_VAULT_PORT:$CAPIF_VAULT_PORT \ + --variable CAPIF_VAULT_TOKEN:$CAPIF_VAULT_TOKEN \ --variable MOCK_SERVER_URL:$MOCK_SERVER_URL $@ diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index c1ea4c7..d60f5c7 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -138,6 +138,10 @@ Deletes an individual CAPIF Event Subscription with invalid SubscriptionId ... detail=User not authorized ... cause=You are not the owner of this resource +# Prueba Mock Server +# [Tags] jms-3 +# Start Mock server + Prueba JMS [Tags] jms-1 diff --git a/tests/resources/common.resource b/tests/resources/common.resource index 4f8136e..9521310 100644 --- a/tests/resources/common.resource +++ b/tests/resources/common.resource @@ -95,9 +95,11 @@ Start Mock server Log Starting mock Server for Robot Framework. # Run Process pip3 install -r /opt/robot-tests/tests/libraries/mock_server/requirements.txt - ${server_process}= Start Process python3 /opt/robot-tests/tests/libraries/mock_server/mock_server.py - Log PID: ${server_process.pid} - + ${server_process}= Start Process python3 /opt/robot-tests/tests/libraries/mock_server/mock_server.py shell=True + # Log PID: ${server_process.pid} + Log output: ${server_process.stdout} + + Sleep 5m Create Session mockserver ${MOCK_SERVER_URL} ${endpoint}= Set Variable /testing -- GitLab From 57a814b290d2a580556926c25c0269f36e7aa0f5 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Wed, 22 May 2024 15:22:18 +0200 Subject: [PATCH 10/26] Minor improbe con common library --- .../CAPIF Api Events/capif_events_api.robot | 212 +++++++++--------- tests/resources/common/basicRequests.robot | 1 + 2 files changed, 107 insertions(+), 106 deletions(-) diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index d60f5c7..2c977c0 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -142,112 +142,112 @@ Deletes an individual CAPIF Event Subscription with invalid SubscriptionId # [Tags] jms-3 # Start Mock server -Prueba JMS - [Tags] jms-1 - - # Start Mock server - Check Mock Server - Clean Mock Server - - # Register APF - ${register_user_info}= Provider Default Registration - - # Publish one api - Publish Service Api ${register_user_info} - - # Register INVOKER - ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding - - ${discover_response}= Get Request Capif - ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} - ... server=${CAPIF_HTTPS_URL} - ... verify=ca.crt - ... username=${INVOKER_USERNAME} - - ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} - - # Subscribe to events - ${events_list}= Create List SERVICE_API_INVOCATION_SUCCESS SERVICE_API_INVOCATION_FAILURE - ${request_body}= Create Events Subscription - ... events=@{events_list} - ... notificationDestination=http://192.168.0.14:9090/testing - ${resp}= Post Request Capif - ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions - ... json=${request_body} - ... server=${CAPIF_HTTPS_URL} - ... verify=ca.crt - ... username=${INVOKER_USERNAME} - - # Check Results - Check Response Variable Type And Values ${resp} 201 EventSubscription - ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} - - ${results}= Create List 200 400 - - # Create Log Entry - ${request_body}= Create Log Entry - ... ${register_user_info['aef_id']} - ... ${register_user_info_invoker['api_invoker_id']} - ... ${api_ids} - ... ${api_names} - ... results=${results} - ${resp}= Post Request Capif - ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs - ... json=${request_body} - ... server=${CAPIF_HTTPS_URL} - ... verify=ca.crt - ... username=${AEF_PROVIDER_USERNAME} - - # Check Results - Check Response Variable Type And Values ${resp} 201 InvocationLog - ${resource_url}= Check Location Header ${resp} ${LOCATION_LOGGING_RESOURCE_REGEX} - - Sleep 3s - - # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. - ${resp}= Get Mock Server Messages - ${notification_events_on_mock_server}= Set Variable ${resp.json()} - # Check if message follow EventNotification definition. - Check Variable ${notification_events_on_mock_server} EventNotification - # Check if mock server receive 2 events. - Length Should Be ${notification_events_on_mock_server} 2 - - # Now we create the expected events received at notification server according to logs sent to loggin service in order to check if all are present. - ${invocation_log_base}= Copy Dictionary ${request_body} deepcopy=True - - # Store log array because each log will be notified in one Event Notification - ${invocation_log_logs}= Set Variable ${invocation_log_base['logs']} - # Remove logs array from invocationLog data - Remove From Dictionary ${invocation_log_base} logs - Length Should Be ${invocation_log_logs} 2 - - # Create 2 invocationLogs from initial invocationLog present on request - ${invocation_logs_1}= Copy Dictionary ${invocation_log_base} deepcopy=True - ${invocation_logs_2}= Copy Dictionary ${invocation_log_base} deepcopy=True - # Create a log array with only one component - ${log_1}= Create List ${invocation_log_logs[0]} - ${log_2}= Create List ${invocation_log_logs[1]} - # Setup logs array with previously created list - Set To Dictionary ${invocation_logs_1} logs=${log_1} - Set To Dictionary ${invocation_logs_2} logs=${log_2} - # Create event details for each log - ${event_details_1}= Create dictionary invocationLogs=${invocation_logs_1} - ${event_details_2}= Create dictionary invocationLogs=${invocation_logs_2} - # Create Event with Event Details from invocationLog - ${event_1}= Create Dictionary - ... subscriptionId=${subscription_id} - ... events=SERVICE_API_INVOCATION_SUCCESS - ... eventDetail=${event_details_1} - ${event_2}= Create Dictionary - ... subscriptionId=${subscription_id} - ... events=SERVICE_API_INVOCATION_FAILURE - ... eventDetail=${event_details_2} - # Check if created events follow EventNotification format defined by 3gpp - Check Variable ${event_1} EventNotification - Check Variable ${event_2} EventNotification - - List Should Contain Value ${notification_events_on_mock_server} ${event_1} - List Should Contain Value ${notification_events_on_mock_server} ${event_2} +# Prueba JMS +# [Tags] jms-1 + +# # Start Mock server +# Check Mock Server +# Clean Mock Server + +# # Register APF +# ${register_user_info}= Provider Default Registration + +# # Publish one api +# Publish Service Api ${register_user_info} + +# # Register INVOKER +# ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + +# ${discover_response}= Get Request Capif +# ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} +# ... server=${CAPIF_HTTPS_URL} +# ... verify=ca.crt +# ... username=${INVOKER_USERNAME} + +# ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + +# # Subscribe to events +# ${events_list}= Create List SERVICE_API_INVOCATION_SUCCESS SERVICE_API_INVOCATION_FAILURE +# ${request_body}= Create Events Subscription +# ... events=@{events_list} +# ... notificationDestination=http://192.168.0.14:9090/testing +# ${resp}= Post Request Capif +# ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions +# ... json=${request_body} +# ... server=${CAPIF_HTTPS_URL} +# ... verify=ca.crt +# ... username=${INVOKER_USERNAME} + +# # Check Results +# Check Response Variable Type And Values ${resp} 201 EventSubscription +# ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + +# ${results}= Create List 200 400 + +# # Create Log Entry +# ${request_body}= Create Log Entry +# ... ${register_user_info['aef_id']} +# ... ${register_user_info_invoker['api_invoker_id']} +# ... ${api_ids} +# ... ${api_names} +# ... results=${results} +# ${resp}= Post Request Capif +# ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs +# ... json=${request_body} +# ... server=${CAPIF_HTTPS_URL} +# ... verify=ca.crt +# ... username=${AEF_PROVIDER_USERNAME} + +# # Check Results +# Check Response Variable Type And Values ${resp} 201 InvocationLog +# ${resource_url}= Check Location Header ${resp} ${LOCATION_LOGGING_RESOURCE_REGEX} + +# Sleep 3s + +# # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. +# ${resp}= Get Mock Server Messages +# ${notification_events_on_mock_server}= Set Variable ${resp.json()} +# # Check if message follow EventNotification definition. +# Check Variable ${notification_events_on_mock_server} EventNotification +# # Check if mock server receive 2 events. +# Length Should Be ${notification_events_on_mock_server} 2 + +# # Now we create the expected events received at notification server according to logs sent to loggin service in order to check if all are present. +# ${invocation_log_base}= Copy Dictionary ${request_body} deepcopy=True + +# # Store log array because each log will be notified in one Event Notification +# ${invocation_log_logs}= Set Variable ${invocation_log_base['logs']} +# # Remove logs array from invocationLog data +# Remove From Dictionary ${invocation_log_base} logs +# Length Should Be ${invocation_log_logs} 2 + +# # Create 2 invocationLogs from initial invocationLog present on request +# ${invocation_logs_1}= Copy Dictionary ${invocation_log_base} deepcopy=True +# ${invocation_logs_2}= Copy Dictionary ${invocation_log_base} deepcopy=True +# # Create a log array with only one component +# ${log_1}= Create List ${invocation_log_logs[0]} +# ${log_2}= Create List ${invocation_log_logs[1]} +# # Setup logs array with previously created list +# Set To Dictionary ${invocation_logs_1} logs=${log_1} +# Set To Dictionary ${invocation_logs_2} logs=${log_2} +# # Create event details for each log +# ${event_details_1}= Create dictionary invocationLogs=${invocation_logs_1} +# ${event_details_2}= Create dictionary invocationLogs=${invocation_logs_2} +# # Create Event with Event Details from invocationLog +# ${event_1}= Create Dictionary +# ... subscriptionId=${subscription_id} +# ... events=SERVICE_API_INVOCATION_SUCCESS +# ... eventDetail=${event_details_1} +# ${event_2}= Create Dictionary +# ... subscriptionId=${subscription_id} +# ... events=SERVICE_API_INVOCATION_FAILURE +# ... eventDetail=${event_details_2} +# # Check if created events follow EventNotification format defined by 3gpp +# Check Variable ${event_1} EventNotification +# Check Variable ${event_2} EventNotification + +# List Should Contain Value ${notification_events_on_mock_server} ${event_1} +# List Should Contain Value ${notification_events_on_mock_server} ${event_2} Prueba JMS [Tags] jms-2 diff --git a/tests/resources/common/basicRequests.robot b/tests/resources/common/basicRequests.robot index 5872a57..9565fb0 100644 --- a/tests/resources/common/basicRequests.robot +++ b/tests/resources/common/basicRequests.robot @@ -457,6 +457,7 @@ Get Auth For User ${resp}= GET On Session register_session /getauth auth=${auth} Should Be Equal As Strings ${resp.status_code} 200 + Log Dictionary ${resp.json()} RETURN ${resp.json()} -- GitLab From a90c0d503de1069508327fbf8f0368b8e9746b42 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Wed, 22 May 2024 16:53:19 +0200 Subject: [PATCH 11/26] Rename new test under events suite --- services/run_capif_tests.sh | 2 +- .../CAPIF Api Events/capif_events_api.robot | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/services/run_capif_tests.sh b/services/run_capif_tests.sh index 944e3b2..4da8622 100755 --- a/services/run_capif_tests.sh +++ b/services/run_capif_tests.sh @@ -25,7 +25,7 @@ CAPIF_VAULT_PORT=8200 # CAPIF_VAULT_TOKEN=dev-only-token CAPIF_VAULT_TOKEN=read-ca-token -MOCK_SERVER_URL=http://mockserver:9090 +MOCK_SERVER_URL=http://192.168.0.14:9090 echo "CAPIF_HOSTNAME = $CAPIF_HOSTNAME" diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index 2c977c0..afb44da 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -249,8 +249,8 @@ Deletes an individual CAPIF Event Subscription with invalid SubscriptionId # List Should Contain Value ${notification_events_on_mock_server} ${event_1} # List Should Contain Value ${notification_events_on_mock_server} ${event_2} -Prueba JMS - [Tags] jms-2 +Invoker receives Service API Invocation events + [Tags] capif_api_events-6 mockserver # Start Mock server Check Mock Server @@ -275,9 +275,14 @@ Prueba JMS # Subscribe to events ${events_list}= Create List SERVICE_API_INVOCATION_SUCCESS SERVICE_API_INVOCATION_FAILURE + ${aef_ids}= Create List ${register_user_info['aef_id']} + ${event_filter}= Create Capif Event Filter aefIds=${aef_ids} + ${event_filters}= Create List ${event_filter} + ${request_body}= Create Events Subscription ... events=@{events_list} - ... notificationDestination=http://192.168.0.14:9090/testing + ... notificationDestination=${MOCK_SERVER_URL}/testing + ... eventFilters=${event_filters} ${resp}= Post Request Capif ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions ... json=${request_body} @@ -289,9 +294,8 @@ Prueba JMS Check Response Variable Type And Values ${resp} 201 EventSubscription ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + # Create Log Entry, emulate success and failure api invocation ${results}= Create List 200 400 - - # Create Log Entry ${request_body}= Create Log Entry ... ${register_user_info['aef_id']} ... ${register_user_info_invoker['api_invoker_id']} -- GitLab From 25540b00ca94a0142a45f81015124e10afdbc740 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Thu, 23 May 2024 13:43:35 +0200 Subject: [PATCH 12/26] SERVICE_API_AVAILABLE and UNAVAILABLE implemented, also the test related --- .../core/invocationlogs.py | 3 +- .../api_invocation_logs/core/redis_event.py | 34 ++- .../controllers/default_controller.py | 6 +- .../published_apis/core/redis_event.py | 40 ++++ services/run_capif_tests.sh | 2 +- .../CAPIF Api Events/capif_events_api.robot | 210 ++++++++---------- tests/libraries/api_events/bodyRequests.py | 48 ++++ tests/resources/common/basicRequests.robot | 10 +- 8 files changed, 217 insertions(+), 136 deletions(-) create mode 100644 services/TS29222_CAPIF_Publish_Service_API/published_apis/core/redis_event.py diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py index 5a11c59..00cf9b7 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py @@ -103,7 +103,8 @@ class LoggingInvocationOperations(Resource): current_app.logger.info(event) invocation_log_base['logs']=[log] - RedisEvent(event,invocation_log_base,"invocationLogs").send_event() + invocationLogs=[invocation_log_base] + RedisEvent(event,"invocationLogs",invocationLogs).send_event() current_app.logger.debug("After log check") diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py index d42a1ef..037eade 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py @@ -4,19 +4,37 @@ import json publisher_ops = Publisher() + class RedisEvent(): - def __init__(self, event, information, event_detail_key) -> None: - self.redis_event={ + def __init__(self, event, event_detail_key, information) -> None: + self.EVENTS_ENUM = [ + 'SERVICE_API_AVAILABLE', + 'SERVICE_API_UNAVAILABLE', + 'SERVICE_API_UPDATE', + 'API_INVOKER_ONBOARDED', + 'API_INVOKER_OFFBOARDED', + 'SERVICE_API_INVOCATION_SUCCESS', + 'SERVICE_API_INVOCATION_FAILURE', + 'ACCESS_CONTROL_POLICY_UPDATE', + 'ACCESS_CONTROL_POLICY_UNAVAILABLE', + 'API_INVOKER_AUTHORIZATION_REVOKED', + 'API_INVOKER_UPDATED', + 'API_TOPOLOGY_HIDING_CREATED', + 'API_TOPOLOGY_HIDING_REVOKED'] + if event not in self.EVENTS_ENUM: + raise Exception( + "Event (" + event + ") is not on event enum (" + ','.join(self.EVENTS_ENUM) + ")") + self.redis_event = { "event": event, "key": event_detail_key, - "information":information + "information": information } - + def to_string(self): return json.dumps(self.redis_event, cls=JSONEncoder) - + def send_event(self): - publisher_ops.publish_message("events-log",self.to_string()) - + publisher_ops.publish_message("events-log", self.to_string()) + def __call__(self): - return self.redis_event \ No newline at end of file + return self.redis_event diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/controllers/default_controller.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/controllers/default_controller.py index 8fc2b62..6636803 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/controllers/default_controller.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/controllers/default_controller.py @@ -15,6 +15,7 @@ from cryptography.hazmat.backends import default_backend from ..core.validate_user import ControlAccess from functools import wraps import pymongo +from ..core.redis_event import RedisEvent service_operations = PublishServiceOperations() @@ -84,7 +85,8 @@ def apf_id_service_apis_post(apf_id, body): # noqa: E501 if res.status_code == 201: current_app.logger.info("Service published") - publisher_ops.publish_message("events", "SERVICE_API_AVAILABLE") + api_id=res.headers['Location'].split('/')[-1] + RedisEvent("SERVICE_API_AVAILABLE", "apiIds", [api_id] ).send_event() return res @@ -107,7 +109,7 @@ def apf_id_service_apis_service_api_id_delete(service_api_id, apf_id): # noqa: if res.status_code == 204: current_app.logger.info("Removed service published") - publisher_ops.publish_message("events", "SERVICE_API_UNAVAILABLE") + RedisEvent("SERVICE_API_UNAVAILABLE", "apiIds", [service_api_id] ).send_event() publisher_ops.publish_message("internal-messages", f"service-removed:{service_api_id}") return res diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/redis_event.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/redis_event.py new file mode 100644 index 0000000..037eade --- /dev/null +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/redis_event.py @@ -0,0 +1,40 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + + +class RedisEvent(): + def __init__(self, event, event_detail_key, information) -> None: + self.EVENTS_ENUM = [ + 'SERVICE_API_AVAILABLE', + 'SERVICE_API_UNAVAILABLE', + 'SERVICE_API_UPDATE', + 'API_INVOKER_ONBOARDED', + 'API_INVOKER_OFFBOARDED', + 'SERVICE_API_INVOCATION_SUCCESS', + 'SERVICE_API_INVOCATION_FAILURE', + 'ACCESS_CONTROL_POLICY_UPDATE', + 'ACCESS_CONTROL_POLICY_UNAVAILABLE', + 'API_INVOKER_AUTHORIZATION_REVOKED', + 'API_INVOKER_UPDATED', + 'API_TOPOLOGY_HIDING_CREATED', + 'API_TOPOLOGY_HIDING_REVOKED'] + if event not in self.EVENTS_ENUM: + raise Exception( + "Event (" + event + ") is not on event enum (" + ','.join(self.EVENTS_ENUM) + ")") + self.redis_event = { + "event": event, + "key": event_detail_key, + "information": information + } + + def to_string(self): + return json.dumps(self.redis_event, cls=JSONEncoder) + + def send_event(self): + publisher_ops.publish_message("events-log", self.to_string()) + + def __call__(self): + return self.redis_event diff --git a/services/run_capif_tests.sh b/services/run_capif_tests.sh index 4da8622..90be28d 100755 --- a/services/run_capif_tests.sh +++ b/services/run_capif_tests.sh @@ -25,7 +25,7 @@ CAPIF_VAULT_PORT=8200 # CAPIF_VAULT_TOKEN=dev-only-token CAPIF_VAULT_TOKEN=read-ca-token -MOCK_SERVER_URL=http://192.168.0.14:9090 +MOCK_SERVER_URL=http://10.95.115.22:9090 echo "CAPIF_HOSTNAME = $CAPIF_HOSTNAME" diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index afb44da..0e9ebf8 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -2,6 +2,7 @@ Resource /opt/robot-tests/tests/resources/common.resource Library /opt/robot-tests/tests/libraries/bodyRequests.py Library XML +Library String Resource /opt/robot-tests/tests/resources/common/basicRequests.robot Resource ../../resources/common.resource @@ -138,119 +139,8 @@ Deletes an individual CAPIF Event Subscription with invalid SubscriptionId ... detail=User not authorized ... cause=You are not the owner of this resource -# Prueba Mock Server -# [Tags] jms-3 -# Start Mock server - -# Prueba JMS -# [Tags] jms-1 - -# # Start Mock server -# Check Mock Server -# Clean Mock Server - -# # Register APF -# ${register_user_info}= Provider Default Registration - -# # Publish one api -# Publish Service Api ${register_user_info} - -# # Register INVOKER -# ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding - -# ${discover_response}= Get Request Capif -# ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} -# ... server=${CAPIF_HTTPS_URL} -# ... verify=ca.crt -# ... username=${INVOKER_USERNAME} - -# ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} - -# # Subscribe to events -# ${events_list}= Create List SERVICE_API_INVOCATION_SUCCESS SERVICE_API_INVOCATION_FAILURE -# ${request_body}= Create Events Subscription -# ... events=@{events_list} -# ... notificationDestination=http://192.168.0.14:9090/testing -# ${resp}= Post Request Capif -# ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions -# ... json=${request_body} -# ... server=${CAPIF_HTTPS_URL} -# ... verify=ca.crt -# ... username=${INVOKER_USERNAME} - -# # Check Results -# Check Response Variable Type And Values ${resp} 201 EventSubscription -# ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} - -# ${results}= Create List 200 400 - -# # Create Log Entry -# ${request_body}= Create Log Entry -# ... ${register_user_info['aef_id']} -# ... ${register_user_info_invoker['api_invoker_id']} -# ... ${api_ids} -# ... ${api_names} -# ... results=${results} -# ${resp}= Post Request Capif -# ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs -# ... json=${request_body} -# ... server=${CAPIF_HTTPS_URL} -# ... verify=ca.crt -# ... username=${AEF_PROVIDER_USERNAME} - -# # Check Results -# Check Response Variable Type And Values ${resp} 201 InvocationLog -# ${resource_url}= Check Location Header ${resp} ${LOCATION_LOGGING_RESOURCE_REGEX} - -# Sleep 3s - -# # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. -# ${resp}= Get Mock Server Messages -# ${notification_events_on_mock_server}= Set Variable ${resp.json()} -# # Check if message follow EventNotification definition. -# Check Variable ${notification_events_on_mock_server} EventNotification -# # Check if mock server receive 2 events. -# Length Should Be ${notification_events_on_mock_server} 2 - -# # Now we create the expected events received at notification server according to logs sent to loggin service in order to check if all are present. -# ${invocation_log_base}= Copy Dictionary ${request_body} deepcopy=True - -# # Store log array because each log will be notified in one Event Notification -# ${invocation_log_logs}= Set Variable ${invocation_log_base['logs']} -# # Remove logs array from invocationLog data -# Remove From Dictionary ${invocation_log_base} logs -# Length Should Be ${invocation_log_logs} 2 - -# # Create 2 invocationLogs from initial invocationLog present on request -# ${invocation_logs_1}= Copy Dictionary ${invocation_log_base} deepcopy=True -# ${invocation_logs_2}= Copy Dictionary ${invocation_log_base} deepcopy=True -# # Create a log array with only one component -# ${log_1}= Create List ${invocation_log_logs[0]} -# ${log_2}= Create List ${invocation_log_logs[1]} -# # Setup logs array with previously created list -# Set To Dictionary ${invocation_logs_1} logs=${log_1} -# Set To Dictionary ${invocation_logs_2} logs=${log_2} -# # Create event details for each log -# ${event_details_1}= Create dictionary invocationLogs=${invocation_logs_1} -# ${event_details_2}= Create dictionary invocationLogs=${invocation_logs_2} -# # Create Event with Event Details from invocationLog -# ${event_1}= Create Dictionary -# ... subscriptionId=${subscription_id} -# ... events=SERVICE_API_INVOCATION_SUCCESS -# ... eventDetail=${event_details_1} -# ${event_2}= Create Dictionary -# ... subscriptionId=${subscription_id} -# ... events=SERVICE_API_INVOCATION_FAILURE -# ... eventDetail=${event_details_2} -# # Check if created events follow EventNotification format defined by 3gpp -# Check Variable ${event_1} EventNotification -# Check Variable ${event_2} EventNotification - -# List Should Contain Value ${notification_events_on_mock_server} ${event_1} -# List Should Contain Value ${notification_events_on_mock_server} ${event_2} - Invoker receives Service API Invocation events - [Tags] capif_api_events-6 mockserver + [Tags] capif_api_events-6 mockserver # Start Mock server Check Mock Server @@ -275,9 +165,9 @@ Invoker receives Service API Invocation events # Subscribe to events ${events_list}= Create List SERVICE_API_INVOCATION_SUCCESS SERVICE_API_INVOCATION_FAILURE - ${aef_ids}= Create List ${register_user_info['aef_id']} - ${event_filter}= Create Capif Event Filter aefIds=${aef_ids} - ${event_filters}= Create List ${event_filter} + ${aef_ids}= Create List ${register_user_info['aef_id']} + ${event_filter}= Create Capif Event Filter aefIds=${aef_ids} + ${event_filters}= Create List ${event_filter} ${request_body}= Create Events Subscription ... events=@{events_list} @@ -322,7 +212,7 @@ Invoker receives Service API Invocation events Check Variable ${notification_events_on_mock_server} EventNotification # Create check Events to ensure all notifications were received - ${check_events} ${check_events_length}= Create Events From invocationLogs + ${check_events} ${check_events_length}= Create Events From InvocationLogs ... ${subscription_id} ... ${request_body} @@ -333,3 +223,91 @@ Invoker receives Service API Invocation events Log ${event} List Should Contain Value ${notification_events_on_mock_server} ${event} END + +Invoker subscribe to Service API Available and Unavailable events + [Tags] capif_api_events-7 mockserver + + # Start Mock server + Check Mock Server + Clean Mock Server + + # Register APF + ${register_user_info_provider}= Provider Default Registration + + # Publish one api + ${service_api_description_published_1} ${resource_url_1} ${request_body}= Publish Service Api + ... ${register_user_info_provider} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Subscribe to events + ${events_list}= Create List SERVICE_API_AVAILABLE SERVICE_API_UNAVAILABLE + ${aef_ids}= Create List ${register_user_info_provider['aef_id']} + ${event_filter}= Create Capif Event Filter aefIds=${aef_ids} + ${event_filters}= Create List ${event_filter} + + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=${MOCK_SERVER_URL}/testing + ... eventFilters=${event_filters} + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + + # Provider publish new API + ${service_api_description_published_2} ${resource_url_2} ${request_body}= Publish Service Api + ... ${register_user_info_provider} + ... service_2 + + # Provider Remove service_1 published API + ${resp}= Delete Request Capif + ... ${resource_url_1.path} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${APF_PROVIDER_USERNAME} + + Status Should Be 204 ${resp} + + # Check Results + + Sleep 3s + # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. + ${resp}= Get Mock Server Messages + ${notification_events_on_mock_server}= Set Variable ${resp.json()} + # Check if message follow EventNotification definition. + Check Variable ${notification_events_on_mock_server} EventNotification + + # Create Notification Events expected to be received + ${api_id_1}= Fetch From Right ${resource_url_1.path} / + ${notification_event_expected_removed}= Create Notification Event + ... ${subscription_id} + ... SERVICE_API_UNAVAILABLE + ... apiIds=${api_id_1} + Check Variable ${notification_event_expected_removed} EventNotification + ${api_id_2}= Fetch From Right ${resource_url_2.path} / + ${notification_event_expected_created}= Create Notification Event + ... ${subscription_id} + ... SERVICE_API_AVAILABLE + ... apiIds=${api_id_2} + Check Variable ${notification_event_expected_created} EventNotification + + # Check results + Length Should Be ${notification_events_on_mock_server} 2 + List Should Contain Value ${notification_events_on_mock_server} ${notification_event_expected_removed} + List Should Contain Value ${notification_events_on_mock_server} ${notification_event_expected_created} diff --git a/tests/libraries/api_events/bodyRequests.py b/tests/libraries/api_events/bodyRequests.py index 3d1de26..24f22f3 100644 --- a/tests/libraries/api_events/bodyRequests.py +++ b/tests/libraries/api_events/bodyRequests.py @@ -45,3 +45,51 @@ def create_websock_notif_config_default(): "websocketUri": "websocketUri" } +def create_notification_event(subscriptionId, event, serviceAPIDescriptions=None, apiIds=None, apiInvokerIds=None, accCtrlPolList=None, invocationLogs=None, apiTopoHide=None): + result={ + "subscriptionId":subscriptionId, + "events": event, + "eventDetail": dict() + } + count=0 + if serviceAPIDescriptions != None: + if isinstance(serviceAPIDescriptions,list()): + result['eventDetail']['serviceAPIDescriptions']=serviceAPIDescriptions + else: + result['eventDetail']['serviceAPIDescriptions']=[serviceAPIDescriptions] + count=count+1 + if apiIds != None: + if isinstance(apiIds,list): + result['eventDetail']['apiIds']=apiIds + else: + result['eventDetail']['apiIds']=[apiIds] + count=count+1 + if apiInvokerIds != None: + if isinstance(apiInvokerIds,list): + result['eventDetail']['apiInvokerIds']=apiInvokerIds + else: + result['eventDetail']['apiInvokerIds']=[apiInvokerIds] + count=count+1 + if accCtrlPolList != None: + if isinstance(accCtrlPolList,list): + result['eventDetail']['accCtrlPolList']=accCtrlPolList + else: + result['eventDetail']['accCtrlPolList']=[accCtrlPolList] + count=count+1 + if invocationLogs != None: + if isinstance(invocationLogs,list): + result['eventDetail']['invocationLogs']=invocationLogs + else: + result['eventDetail']['invocationLogs']=[invocationLogs] + count=count+1 + if apiTopoHide != None: + if isinstance(apiTopoHide): + result['eventDetail']['apiTopoHide']=apiTopoHide + else: + result['eventDetail']['apiTopoHide']=[apiTopoHide] + count=count+1 + + if count == 0: + del result['eventDetail'] + + return result diff --git a/tests/resources/common/basicRequests.robot b/tests/resources/common/basicRequests.robot index 9565fb0..9ba5f21 100644 --- a/tests/resources/common/basicRequests.robot +++ b/tests/resources/common/basicRequests.robot @@ -747,7 +747,7 @@ Create Security Context Between invoker and provider Check Response Variable Type And Values ${resp} 201 ServiceSecurity -Create Events From invocationLogs +Create Events From InvocationLogs [Arguments] ${subscription_id} ${invocation_log} ${events}= Create List @@ -774,13 +774,7 @@ Create Events From invocationLogs ${log_list}= Create List ${log} # Setup logs array with previously created list Set To Dictionary ${invocation_logs} logs=${log_list} - # Create event details for each log - ${event_details}= Create dictionary invocationLogs=${invocation_logs} - # Create Event with Event Details from invocationLog and also is appended to events array - ${event}= Create Dictionary - ... subscriptionId=${subscription_id} - ... events=${event_enum} - ... eventDetail=${event_details} + ${event}= Create Notification Event ${subscription_id} ${event_enum} invocationLogs=${invocation_logs} Check Variable ${event} EventNotification Append To List ${events} ${event} END -- GitLab From 03f83118009e805a05f632f98bba884f7d94204b Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Thu, 23 May 2024 17:16:40 +0200 Subject: [PATCH 13/26] SERVICE_API_UPDATE upgraded --- .../controllers/default_controller.py | 41 ++++------ .../core/serviceapidescriptions.py | 70 +++++++++++----- services/run_capif_tests.sh | 2 +- .../CAPIF Api Events/capif_events_api.robot | 81 +++++++++++++++++++ tests/libraries/api_events/bodyRequests.py | 4 +- .../api_publish_service/bodyRequests.py | 17 +--- 6 files changed, 150 insertions(+), 65 deletions(-) diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/controllers/default_controller.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/controllers/default_controller.py index 6636803..dd43886 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/controllers/default_controller.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/controllers/default_controller.py @@ -4,7 +4,6 @@ from ..core import serviceapidescriptions from ..core.serviceapidescriptions import PublishServiceOperations from ..core.publisher import Publisher -import json from flask import Response, request, current_app from flask_jwt_extended import jwt_required, get_jwt_identity from flask import current_app @@ -14,15 +13,12 @@ from cryptography import x509 from cryptography.hazmat.backends import default_backend from ..core.validate_user import ControlAccess from functools import wraps -import pymongo -from ..core.redis_event import RedisEvent - service_operations = PublishServiceOperations() -publisher_ops = Publisher() valid_user = ControlAccess() + def cert_validation(): def _cert_validation(f): @wraps(f) @@ -32,13 +28,16 @@ def cert_validation(): cert_tmp = request.headers['X-Ssl-Client-Cert'] cert_raw = cert_tmp.replace('\t', '') - cert = x509.load_pem_x509_certificate(str.encode(cert_raw), default_backend()) + cert = x509.load_pem_x509_certificate( + str.encode(cert_raw), default_backend()) - cn = cert.subject.get_attributes_for_oid(x509.OID_COMMON_NAME)[0].value.strip() + cn = cert.subject.get_attributes_for_oid( + x509.OID_COMMON_NAME)[0].value.strip() if cn != "superadmin": cert_signature = cert.signature.hex() - result = valid_user.validate_user_cert(args["apfId"], args["serviceApiId"], cert_signature) + result = valid_user.validate_user_cert( + args["apfId"], args["serviceApiId"], cert_signature) if result is not None: return result @@ -48,6 +47,7 @@ def cert_validation(): return __cert_validation return _cert_validation + def apf_id_service_apis_get(apf_id): # noqa: E501 """apf_id_service_apis_get @@ -83,13 +83,9 @@ def apf_id_service_apis_post(apf_id, body): # noqa: E501 res = service_operations.add_serviceapidescription(apf_id, body) - if res.status_code == 201: - current_app.logger.info("Service published") - api_id=res.headers['Location'].split('/')[-1] - RedisEvent("SERVICE_API_AVAILABLE", "apiIds", [api_id] ).send_event() - return res + @cert_validation() def apf_id_service_apis_service_api_id_delete(service_api_id, apf_id): # noqa: E501 """apf_id_service_apis_service_api_id_delete @@ -105,15 +101,12 @@ def apf_id_service_apis_service_api_id_delete(service_api_id, apf_id): # noqa: """ current_app.logger.info("Removing service published") - res = service_operations.delete_serviceapidescription(service_api_id, apf_id) - - if res.status_code == 204: - current_app.logger.info("Removed service published") - RedisEvent("SERVICE_API_UNAVAILABLE", "apiIds", [service_api_id] ).send_event() - publisher_ops.publish_message("internal-messages", f"service-removed:{service_api_id}") + res = service_operations.delete_serviceapidescription( + service_api_id, apf_id) return res + @cert_validation() def apf_id_service_apis_service_api_id_get(service_api_id, apf_id): # noqa: E501 """apf_id_service_apis_service_api_id_get @@ -133,6 +126,7 @@ def apf_id_service_apis_service_api_id_get(service_api_id, apf_id): # noqa: E50 return res + @cert_validation() def apf_id_service_apis_service_api_id_put(service_api_id, apf_id, body): # noqa: E501 """apf_id_service_apis_service_api_id_put @@ -149,14 +143,13 @@ def apf_id_service_apis_service_api_id_put(service_api_id, apf_id, body): # noq :rtype: ServiceAPIDescription """ - current_app.logger.info("Updating service api id with id: " + service_api_id) + current_app.logger.info( + "Updating service api id with id: " + service_api_id) if connexion.request.is_json: body = ServiceAPIDescription.from_dict(connexion.request.get_json()) # noqa: E501 - response = service_operations.update_serviceapidescription(service_api_id, apf_id, body) - - if response.status_code == 200: - publisher_ops.publish_message("events", "SERVICE_API_UPDATE") + response = service_operations.update_serviceapidescription( + service_api_id, apf_id, body) return response diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py index 5c903d1..85b9b78 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py @@ -17,26 +17,33 @@ from ..util import dict_to_camel_case, clean_empty from .responses import bad_request_error, internal_server_error, forbidden_error, not_found_error, unauthorized_error, make_response from bson import json_util from .auth_manager import AuthManager +from .redis_event import RedisEvent +from .publisher import Publisher + +publisher_ops = Publisher() service_api_not_found_message = "Service API not found" + class PublishServiceOperations(Resource): def __check_apf(self, apf_id): providers_col = self.db.get_col_by_name(self.db.capif_provider_col) current_app.logger.debug("Checking apf id") - provider = providers_col.find_one({"api_prov_funcs.api_prov_func_id": apf_id}) + provider = providers_col.find_one( + {"api_prov_funcs.api_prov_func_id": apf_id}) if provider is None: current_app.logger.error("Publisher not exist") - return unauthorized_error(detail = "Publisher not existing", cause = "Publisher id not found") + return unauthorized_error(detail="Publisher not existing", cause="Publisher id not found") - list_apf_ids = [func["api_prov_func_id"] for func in provider["api_prov_funcs"] if func["api_prov_func_role"] == "APF"] + list_apf_ids = [func["api_prov_func_id"] + for func in provider["api_prov_funcs"] if func["api_prov_func_role"] == "APF"] if apf_id not in list_apf_ids: current_app.logger.debug("This id not belongs to APF") - return unauthorized_error(detail ="You are not a publisher", cause ="This API is only available for publishers") + return unauthorized_error(detail="You are not a publisher", cause="This API is only available for publishers") return None @@ -57,7 +64,8 @@ class PublishServiceOperations(Resource): if result != None: return result - service = mycol.find({"apf_id": apf_id}, {"_id":0, "api_name":1, "api_id":1, "aef_profiles":1, "description":1, "supported_features":1, "shareable_info":1, "service_api_category":1, "api_supp_feats":1, "pub_api_path":1, "ccf_id":1}) + service = mycol.find({"apf_id": apf_id}, {"_id": 0, "api_name": 1, "api_id": 1, "aef_profiles": 1, "description": 1, + "supported_features": 1, "shareable_info": 1, "service_api_category": 1, "api_supp_feats": 1, "pub_api_path": 1, "ccf_id": 1}) current_app.logger.debug(service) if service is None: current_app.logger.error("Not found services for this apf id") @@ -92,9 +100,11 @@ class PublishServiceOperations(Resource): if result != None: return result - service = mycol.find_one({"api_name": serviceapidescription.api_name}) + service = mycol.find_one( + {"api_name": serviceapidescription.api_name}) if service is not None: - current_app.logger.error("Service already registered with same api name") + current_app.logger.error( + "Service already registered with same api name") return forbidden_error(detail="Already registered service with same api name", cause="Found service with same api name") api_id = secrets.token_hex(15) @@ -110,8 +120,13 @@ class PublishServiceOperations(Resource): current_app.logger.debug("Service inserted in database") res = make_response(object=serviceapidescription, status=201) - res.headers['Location'] = "http://localhost:8080/published-apis/v1/" + str(apf_id) + "/service-apis/" + str(api_id) + res.headers['Location'] = "http://localhost:8080/published-apis/v1/" + \ + str(apf_id) + "/service-apis/" + str(api_id) + if res.status_code == 201: + current_app.logger.info("Service published") + RedisEvent("SERVICE_API_AVAILABLE", "apiIds", + [str(api_id)]).send_event() return res except Exception as e: @@ -119,26 +134,25 @@ class PublishServiceOperations(Resource): current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - - def get_one_serviceapi(self, service_api_id, apf_id): mycol = self.db.get_col_by_name(self.db.service_api_descriptions) try: - current_app.logger.debug("Geting service api with id: " + service_api_id) + current_app.logger.debug( + "Geting service api with id: " + service_api_id) result = self.__check_apf(apf_id) if result != None: return result my_query = {'apf_id': apf_id, 'api_id': service_api_id} - service_api = mycol.find_one(my_query, {"_id":0, "api_name":1, "api_id":1, "aef_profiles":1, "description":1, "supported_features":1, "shareable_info":1, "service_api_category":1, "api_supp_feats":1, "pub_api_path":1, "ccf_id":1}) + service_api = mycol.find_one(my_query, {"_id": 0, "api_name": 1, "api_id": 1, "aef_profiles": 1, "description": 1, + "supported_features": 1, "shareable_info": 1, "service_api_category": 1, "api_supp_feats": 1, "pub_api_path": 1, "ccf_id": 1}) if service_api is None: current_app.logger.error(service_api_not_found_message) return not_found_error(detail=service_api_not_found_message, cause="No Service with specific credentials exists") - my_service_api = dict_to_camel_case(service_api) my_service_api = clean_empty(my_service_api) @@ -157,7 +171,8 @@ class PublishServiceOperations(Resource): try: - current_app.logger.debug("Removing api service with id: " + service_api_id) + current_app.logger.debug( + "Removing api service with id: " + service_api_id) result = self.__check_apf(apf_id) if result != None: @@ -175,22 +190,29 @@ class PublishServiceOperations(Resource): self.auth_manager.remove_auth_service(service_api_id, apf_id) current_app.logger.debug("Removed service from database") - out = "The service matching api_id " + service_api_id + " was deleted." - return make_response(out, status=204) + out = "The service matching api_id " + service_api_id + " was deleted." + res = make_response(out, status=204) + if res.status_code == 204: + current_app.logger.info("Removed service published") + RedisEvent("SERVICE_API_UNAVAILABLE", "apiIds", + [service_api_id]).send_event() + publisher_ops.publish_message( + "internal-messages", f"service-removed:{service_api_id}") + return res except Exception as e: exception = "An exception occurred in delete service" current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - def update_serviceapidescription(self, service_api_id, apf_id, service_api_description): mycol = self.db.get_col_by_name(self.db.service_api_descriptions) try: - current_app.logger.debug("Updating service api with id: " + service_api_id) + current_app.logger.debug( + "Updating service api with id: " + service_api_id) result = self.__check_apf(apf_id) @@ -207,18 +229,22 @@ class PublishServiceOperations(Resource): service_api_description = service_api_description.to_dict() service_api_description = clean_empty(service_api_description) - result = mycol.find_one_and_update(serviceapidescription, {"$set":service_api_description}, projection={"_id":0, "api_name":1, "api_id":1, "aef_profiles":1, "description":1, "supported_features":1, "shareable_info":1, "service_api_category":1, "api_supp_feats":1, "pub_api_path":1, "ccf_id":1},return_document=ReturnDocument.AFTER ,upsert=False) + result = mycol.find_one_and_update(serviceapidescription, {"$set": service_api_description}, projection={"_id": 0, "api_name": 1, "api_id": 1, "aef_profiles": 1, "description": 1, + "supported_features": 1, "shareable_info": 1, "service_api_category": 1, "api_supp_feats": 1, "pub_api_path": 1, "ccf_id": 1}, return_document=ReturnDocument.AFTER, upsert=False) result = clean_empty(result) current_app.logger.debug("Updated service api") - - response = make_response(object=dict_to_camel_case(result), status=200) + service_api_description_updated = dict_to_camel_case(result) + response = make_response( + object=service_api_description_updated, status=200) + if response.status_code == 200: + RedisEvent("SERVICE_API_UPDATE", "serviceAPIDescriptions", [ + service_api_description_updated]).send_event() return response except Exception as e: exception = "An exception occurred in update service" current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - diff --git a/services/run_capif_tests.sh b/services/run_capif_tests.sh index 90be28d..06de230 100755 --- a/services/run_capif_tests.sh +++ b/services/run_capif_tests.sh @@ -25,7 +25,7 @@ CAPIF_VAULT_PORT=8200 # CAPIF_VAULT_TOKEN=dev-only-token CAPIF_VAULT_TOKEN=read-ca-token -MOCK_SERVER_URL=http://10.95.115.22:9090 +MOCK_SERVER_URL=http://192.168.0.119:9090 echo "CAPIF_HOSTNAME = $CAPIF_HOSTNAME" diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index 0e9ebf8..eabace4 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -311,3 +311,84 @@ Invoker subscribe to Service API Available and Unavailable events Length Should Be ${notification_events_on_mock_server} 2 List Should Contain Value ${notification_events_on_mock_server} ${notification_event_expected_removed} List Should Contain Value ${notification_events_on_mock_server} ${notification_event_expected_created} + + +Invoker subscribe to Service API Update + [Tags] capif_api_events-8 mockserver + + # Start Mock server + Check Mock Server + Clean Mock Server + + # Register APF + ${register_user_info_provider}= Provider Default Registration + + # Publish one api + ${service_api_description_published} ${resource_url} ${request_body}= Publish Service Api + ... ${register_user_info_provider} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Subscribe to events + ${events_list}= Create List SERVICE_API_UPDATE + ${aef_ids}= Create List ${register_user_info_provider['aef_id']} + ${event_filter}= Create Capif Event Filter aefIds=${aef_ids} + ${event_filters}= Create List ${event_filter} + + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=${MOCK_SERVER_URL}/testing + ... eventFilters=${event_filters} + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + + # Update Service API + ${request_body_modified}= Create Service Api Description service_1_modified + ${resp}= Put Request Capif + ... ${resource_url.path} + ... json=${request_body_modified} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${APF_PROVIDER_USERNAME} + + Check Response Variable Type And Values ${resp} 200 ServiceAPIDescription + ... apiName=service_1_modified + + # Check Results + Sleep 3s + # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. + ${resp}= Get Mock Server Messages + ${notification_events_on_mock_server}= Set Variable ${resp.json()} + # Check if message follow EventNotification definition. + Check Variable ${notification_events_on_mock_server} EventNotification + + # Create Notification Events expected to be received + ${api_id}= Fetch From Right ${resource_url.path} / + Set To Dictionary ${request_body_modified} apiId=${api_id} + ${notification_event_expected}= Create Notification Event + ... ${subscription_id} + ... SERVICE_API_UPDATE + ... serviceAPIDescriptions=${request_body_modified} + Check Variable ${notification_event_expected} EventNotification + + # Check results + Length Should Be ${notification_events_on_mock_server} 1 + List Should Contain Value ${notification_events_on_mock_server} ${notification_event_expected} + diff --git a/tests/libraries/api_events/bodyRequests.py b/tests/libraries/api_events/bodyRequests.py index 24f22f3..14c8bb4 100644 --- a/tests/libraries/api_events/bodyRequests.py +++ b/tests/libraries/api_events/bodyRequests.py @@ -53,7 +53,7 @@ def create_notification_event(subscriptionId, event, serviceAPIDescriptions=None } count=0 if serviceAPIDescriptions != None: - if isinstance(serviceAPIDescriptions,list()): + if isinstance(serviceAPIDescriptions,list): result['eventDetail']['serviceAPIDescriptions']=serviceAPIDescriptions else: result['eventDetail']['serviceAPIDescriptions']=[serviceAPIDescriptions] @@ -83,7 +83,7 @@ def create_notification_event(subscriptionId, event, serviceAPIDescriptions=None result['eventDetail']['invocationLogs']=[invocationLogs] count=count+1 if apiTopoHide != None: - if isinstance(apiTopoHide): + if isinstance(apiTopoHide,list): result['eventDetail']['apiTopoHide']=apiTopoHide else: result['eventDetail']['apiTopoHide']=[apiTopoHide] diff --git a/tests/libraries/api_publish_service/bodyRequests.py b/tests/libraries/api_publish_service/bodyRequests.py index 17ac4a7..69e7bb1 100644 --- a/tests/libraries/api_publish_service/bodyRequests.py +++ b/tests/libraries/api_publish_service/bodyRequests.py @@ -7,7 +7,7 @@ def create_service_api_description(api_name="service_1",aef_id="aef_id"): "versions": [ { "apiVersion": "v1", - "expiry": "2021-11-30T10:32:02.004000Z", + "expiry": "2021-11-30T10:32:02.004000+00:00", "resources": [ { "resourceName": "string", @@ -20,27 +20,12 @@ def create_service_api_description(api_name="service_1",aef_id="aef_id"): "description": "string" } ], - "custOperations": [ - { - "commType": "REQUEST_RESPONSE", - "custOpName": "string", - "operations": [ - "GET" - ], - "description": "string" - } - ] } ], "protocol": "HTTP_1_1", "dataFormat": "JSON", "securityMethods": ["PSK"], "interfaceDescriptions": [ - { - "ipv4Addr": "string", - "port": 65535, - "securityMethods": ["PSK"] - }, { "ipv4Addr": "string", "port": 65535, -- GitLab From 1624c79be037394dad4b7efb956c82df7adc4c7b Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Mon, 27 May 2024 11:18:24 +0200 Subject: [PATCH 14/26] Adding API Invoker events --- .../controllers/default_controller.py | 13 --- .../core/apiinvokerenrolmentdetails.py | 19 +++- .../core/redis_event.py | 40 +++++++ .../core/provider_enrolment_details_api.py | 2 +- .../capif_events/core/consumer_messager.py | 12 +-- .../capif_events/core/internal_event_ops.py | 29 ++--- .../published_apis/core/consumer_messager.py | 2 +- .../capif_security/core/consumer_messager.py | 8 +- .../CAPIF Api Events/capif_events_api.robot | 100 ++++++++++++++++++ 9 files changed, 172 insertions(+), 53 deletions(-) create mode 100644 services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_event.py diff --git a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/controllers/default_controller.py b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/controllers/default_controller.py index 27eb1c8..12c217c 100644 --- a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/controllers/default_controller.py +++ b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/controllers/default_controller.py @@ -15,7 +15,6 @@ from ..core.publisher import Publisher from functools import wraps invoker_operations = InvokerManagementOperations() -publisher_ops = Publisher() valid_user = ControlAccess() @@ -59,11 +58,6 @@ def onboarded_invokers_onboarding_id_delete(onboarding_id): # noqa: E501 current_app.logger.info("Removing invoker") res = invoker_operations.remove_apiinvokerenrolmentdetail(onboarding_id) - if res.status_code == 204: - current_app.logger.info("Invoker Removed") - publisher_ops.publish_message("events", "API_INVOKER_OFFBOARDED") - publisher_ops.publish_message("internal-messages", f"invoker-removed:{onboarding_id}") - return res @cert_validation() @@ -84,10 +78,6 @@ def onboarded_invokers_onboarding_id_put(onboarding_id, body): # noqa: E501 body = APIInvokerEnrolmentDetails.from_dict(connexion.request.get_json()) # noqa: E501 res = invoker_operations.update_apiinvokerenrolmentdetail(onboarding_id,body) - if res.status_code == 200: - current_app.logger.info("Invoker Updated") - publisher_ops.publish_message("events", "API_INVOKER_UPDATED") - return res @@ -111,8 +101,5 @@ def onboarded_invokers_post(body): # noqa: E501 current_app.logger.info("Creating Invoker") res = invoker_operations.add_apiinvokerenrolmentdetail(body, username, uuid) - if res.status_code == 201: - current_app.logger.info("Invoker Created") - publisher_ops.publish_message("events", "API_INVOKER_ONBOARDED") return res diff --git a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/apiinvokerenrolmentdetails.py b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/apiinvokerenrolmentdetails.py index 2d43af2..3a8ec3f 100644 --- a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/apiinvokerenrolmentdetails.py +++ b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/apiinvokerenrolmentdetails.py @@ -11,9 +11,10 @@ from .auth_manager import AuthManager from .resources import Resource from ..config import Config from api_invoker_management.models.api_invoker_enrolment_details import APIInvokerEnrolmentDetails +from .redis_event import RedisEvent +from .publisher import Publisher - - +publisher_ops = Publisher() class InvokerManagementOperations(Resource): def __check_api_invoker_id(self, api_invoker_id): @@ -93,6 +94,10 @@ class InvokerManagementOperations(Resource): res = make_response(object=apiinvokerenrolmentdetail, status=201) res.headers['Location'] = "/api-invoker-management/v1/onboardedInvokers/" + str(api_invoker_id) + + if res.status_code == 201: + current_app.logger.info("Invoker Created") + RedisEvent("API_INVOKER_ONBOARDED", "apiInvokerIds", [str(api_invoker_id)]).send_event() return res # except Exception as e: @@ -130,6 +135,9 @@ class InvokerManagementOperations(Resource): current_app.logger.debug("Invoker Resource inserted in database") res = make_response(object=APIInvokerEnrolmentDetails().from_dict(dict_to_camel_case(result)), status=200) + if res.status_code == 200: + current_app.logger.info("Invoker Updated") + RedisEvent("API_INVOKER_UPDATED", "apiInvokerIds", [onboard_id]).send_event() return res except Exception as e: @@ -153,7 +161,12 @@ class InvokerManagementOperations(Resource): current_app.logger.debug("Invoker resource removed from database") current_app.logger.debug("Netapp offboarded sucessfuly") out = "The Netapp matching onboardingId " + onboard_id + " was offboarded." - return make_response(out, status=204) + res = make_response(out, status=204) + if res.status_code == 204: + current_app.logger.info("Invoker Removed") + RedisEvent("API_INVOKER_OFFBOARDED", "apiInvokerIds", [onboard_id]).send_event() + publisher_ops.publish_message("internal-messages", f"invoker-removed:{onboard_id}") + return res except Exception as e: exception = "An exception occurred in remove invoker" diff --git a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_event.py b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_event.py new file mode 100644 index 0000000..037eade --- /dev/null +++ b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_event.py @@ -0,0 +1,40 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + + +class RedisEvent(): + def __init__(self, event, event_detail_key, information) -> None: + self.EVENTS_ENUM = [ + 'SERVICE_API_AVAILABLE', + 'SERVICE_API_UNAVAILABLE', + 'SERVICE_API_UPDATE', + 'API_INVOKER_ONBOARDED', + 'API_INVOKER_OFFBOARDED', + 'SERVICE_API_INVOCATION_SUCCESS', + 'SERVICE_API_INVOCATION_FAILURE', + 'ACCESS_CONTROL_POLICY_UPDATE', + 'ACCESS_CONTROL_POLICY_UNAVAILABLE', + 'API_INVOKER_AUTHORIZATION_REVOKED', + 'API_INVOKER_UPDATED', + 'API_TOPOLOGY_HIDING_CREATED', + 'API_TOPOLOGY_HIDING_REVOKED'] + if event not in self.EVENTS_ENUM: + raise Exception( + "Event (" + event + ") is not on event enum (" + ','.join(self.EVENTS_ENUM) + ")") + self.redis_event = { + "event": event, + "key": event_detail_key, + "information": information + } + + def to_string(self): + return json.dumps(self.redis_event, cls=JSONEncoder) + + def send_event(self): + publisher_ops.publish_message("events-log", self.to_string()) + + def __call__(self): + return self.redis_event diff --git a/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/provider_enrolment_details_api.py b/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/provider_enrolment_details_api.py index 6ba9043..399ac74 100644 --- a/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/provider_enrolment_details_api.py +++ b/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/provider_enrolment_details_api.py @@ -93,7 +93,7 @@ class ProviderManagementOperations(Resource): self.auth_manager.remove_auth_provider([apf_id[0], aef_id[0], amf_id[0]]) - self.publish_ops.publish_message("internal-messages", f"provider-removed:{aef_id[0]}:{apf_id[0]}") + self.publish_ops.publish_message("internal-messages", f"provider-removed:{aef_id[0]}:{apf_id[0]}:{amf_id[0]}") return make_response(object=out, status=204) except Exception as e: diff --git a/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py b/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py index fa38290..5012448 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py @@ -24,17 +24,17 @@ class Subscriber(): if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "events": current_app.logger.info("Event received") self.notification.send_notifications(raw_message["data"].decode('utf-8')) - if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "events-log": + elif raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "events-log": current_app.logger.info("Event-log received") event_redis=json.loads(raw_message["data"].decode('utf-8')) current_app.logger.info(json.dumps(event_redis, indent=4)) self.notification.send_notifications_new(event_redis) - - elif raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": - message, *invoker_id = raw_message["data"].decode('utf-8').split(":") - if message == "invoker-removed" and len(invoker_id)>0: - self.event_ops.delete_all_events(invoker_id[0]) + message, *subscriber_ids = raw_message["data"].decode('utf-8').split(":") + if message == "invoker-removed" and len(subscriber_ids)>0: + self.event_ops.delete_all_events(subscriber_ids) + if message == "provider-removed" and len(subscriber_ids)>0: + self.event_ops.delete_all_events(subscriber_ids) diff --git a/services/TS29222_CAPIF_Events_API/capif_events/core/internal_event_ops.py b/services/TS29222_CAPIF_Events_API/capif_events/core/internal_event_ops.py index 437f7e3..e49089a 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/internal_event_ops.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/internal_event_ops.py @@ -8,13 +8,14 @@ class InternalEventOperations(Resource): Resource.__init__(self) self.auth_manager = AuthManager() - def delete_all_events(self, subscriber_id): + def delete_all_events(self, subscriber_ids): - mycol = self.db.get_col_by_name(self.db.event_collection) - my_query = {'subscriber_id': subscriber_id} - mycol.delete_many(my_query) + for subscriber_id in subscriber_ids: + mycol = self.db.get_col_by_name(self.db.event_collection) + my_query = {'subscriber_id': subscriber_id} + mycol.delete_many(my_query) - current_app.logger.info(f"Removed events for this subscriber: {subscriber_id}") + current_app.logger.info(f"Removed events for this subscriber: {subscriber_id}") #We dont need remove all auth events, becase when invoker is removed, remove auth entry #self.auth_manager.remove_auth_all_event(subscriber_id) @@ -39,21 +40,3 @@ class InternalEventOperations(Resource): except Exception as e: current_app.logger.error("An exception occurred ::" + str(e)) return False - - # def get_acls(self, service_id): - # try: - # mycol = self.db.get_col_by_name(self.db.acls_col) - - # query= {'api_id': service_id} - # acls = mycol.find(query) - - # if acls is None: - # current_app.logger.error("Not found event subscriptions") - - # else: - - # return acls - - # except Exception as e: - # current_app.logger.error("An exception occurred ::" + str(e)) - # return False \ No newline at end of file diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/consumer_messager.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/consumer_messager.py index 93d35e4..6f40a04 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/consumer_messager.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/consumer_messager.py @@ -21,7 +21,7 @@ class Subscriber(): for raw_message in self.p.listen(): if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": message, *ids = raw_message["data"].decode('utf-8').split(":") - if message == "provider-removed" and len(ids)==2: + if message == "provider-removed" and len(ids) > 0: self.security_ops.delete_intern_service(ids[1]) diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py b/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py index fd9c328..8bb8574 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py @@ -8,6 +8,7 @@ from threading import Thread from .internal_security_ops import InternalSecurityOps from flask import current_app + class Subscriber(): def __init__(self): @@ -21,12 +22,7 @@ class Subscriber(): for raw_message in self.p.listen(): if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": message, *ids = raw_message["data"].decode('utf-8').split(":") - if message == "invoker-removed" and len(ids)>0: + if message == "invoker-removed" and len(ids) > 0: self.security_ops.delete_intern_servicesecurity(ids[0]) if message == "provider-removed" or message == "service-removed" and len(ids) > 0: self.security_ops.update_intern_servicesecurity(ids[0]) - - - - - diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index eabace4..8d6150e 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -392,3 +392,103 @@ Invoker subscribe to Service API Update Length Should Be ${notification_events_on_mock_server} 1 List Should Contain Value ${notification_events_on_mock_server} ${notification_event_expected} +Provider subscribe to API Invoker events + [Tags] capif_api_events-9 mockserver + + # Start Mock server + Check Mock Server + Clean Mock Server + + # Register APF + ${register_user_info_provider}= Provider Default Registration + + # Subscribe to events + ${events_list}= Create List API_INVOKER_ONBOARDED API_INVOKER_UPDATED API_INVOKER_OFFBOARDED + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=${MOCK_SERVER_URL}/testing + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_provider['amf_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AMF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + # Update Invoker onboarded information + ${new_notification_destination}= Set Variable + ... http://${CAPIF_CALLBACK_IP}:${CAPIF_CALLBACK_PORT}/netapp_new_callback + Set To Dictionary + ... ${request_body} + ... notificationDestination=${new_notification_destination} + ${resp}= Put Request Capif + ... ${url.path} + ... ${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Update + Check Response Variable Type And Values ${resp} 200 APIInvokerEnrolmentDetails + ... notificationDestination=${new_notification_destination} + + # Remove Invoker from CCF + ${resp}= Delete Request Capif + ... ${url.path} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + Call Method ${CAPIF_USERS} remove_capif_users_entry ${url.path} + + # Check Remove + Should Be Equal As Strings ${resp.status_code} 204 + + # Check Results + Sleep 3s + # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. + ${resp}= Get Mock Server Messages + ${notification_events_on_mock_server}= Set Variable ${resp.json()} + # Check if message follow EventNotification definition. + Check Variable ${notification_events_on_mock_server} EventNotification + + ## Create events expected + ${events_expected}= Create List + ${api_invoker_id}= Set Variable ${register_user_info_invoker['api_invoker_id']} + # Create Notification Events expected to be received for Onboard event + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... API_INVOKER_ONBOARDED + ... apiInvokerIds=${api_invoker_id} + Append To List ${events_expected} ${event_expected} + + # Create Notification Events expected to be received for Updated event + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... API_INVOKER_UPDATED + ... apiInvokerIds=${api_invoker_id} + Append To List ${events_expected} ${event_expected} + + # Create Notification Events expected to be received for Offboard event + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... API_INVOKER_OFFBOARDED + ... apiInvokerIds=${api_invoker_id} + Append To List ${events_expected} ${event_expected} + + Check Variable ${events_expected} EventNotification + + # Check results + ${events_expected_length}= Get Length ${event_expected} + Length Should Be ${notification_events_on_mock_server} ${events_expected_length} + FOR ${event_expected} IN @{events_expected} + Log ${event_expected} + List Should Contain Value ${notification_events_on_mock_server} ${event_expected} + END + -- GitLab From 90818df71049f2be7247d987660752b5624f8f93 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Mon, 27 May 2024 20:13:21 +0200 Subject: [PATCH 15/26] Adding accCtrlPolList --- .../core/accesscontrolpolicyapi.py | 4 +- .../core/internal_service_ops.py | 113 +++++++++++------- .../openapi_server/core/redis_event.py | 41 +++++++ .../capif_events/core/notifications.py | 2 +- .../CAPIF Api Events/capif_events_api.robot | 107 ++++++++++++++++- tests/libraries/api_events/bodyRequests.py | 84 ++++++------- 6 files changed, 261 insertions(+), 90 deletions(-) create mode 100644 services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/redis_event.py diff --git a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/accesscontrolpolicyapi.py b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/accesscontrolpolicyapi.py index fd14e80..b5698be 100644 --- a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/accesscontrolpolicyapi.py +++ b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/accesscontrolpolicyapi.py @@ -37,8 +37,8 @@ class accessControlPolicyApi(Resource): current_app.logger.debug(policies) - api_invoker_policies = policies[0]['apiInvokerPolicies'] - current_app.logger.debug(f"apiinvokerPolicies: {api_invoker_policies}") + api_invoker_policies = policies[0]['api_invoker_policies'] + current_app.logger.debug(f"api_invoker_policies: {api_invoker_policies}") if not api_invoker_policies: current_app.logger.info(f"ACLs list is present but empty, then no ACLs found for the requested service: {service_api_id}, aef_id: {aef_id}, invoker: {api_invoker_id} and supportedFeatures: {supported_features}") #Not found error diff --git a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/internal_service_ops.py b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/internal_service_ops.py index e10986a..8d6a66c 100644 --- a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/internal_service_ops.py +++ b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/internal_service_ops.py @@ -4,82 +4,105 @@ from .resources import Resource from ..models.api_invoker_policy import ApiInvokerPolicy from ..models.time_range_list import TimeRangeList from datetime import datetime, timedelta -from ..core.publisher import Publisher +from .redis_event import RedisEvent +from ..util import dict_to_camel_case, clean_empty + -publisher_ops = Publisher() class InternalServiceOps(Resource): - + def create_acl(self, invoker_id, service_id, aef_id): current_app.logger.info(f"Creating ACL for invoker: {invoker_id}") if "acls" not in self.db.db.list_collection_names(): self.db.db.create_collection("acls") - + mycol = self.db.get_col_by_name(self.db.acls) - res = mycol.find_one({"service_id": service_id, "aef_id":aef_id}, {"_id":0}) - + res = mycol.find_one( + {"service_id": service_id, "aef_id": aef_id}, {"_id": 0}) + if res: - current_app.logger.info(f"Adding invoker ACL for invoker {invoker_id}") - range_list = [TimeRangeList(datetime.utcnow(), datetime.utcnow()+timedelta(days=365))] - invoker_acl = ApiInvokerPolicy(invoker_id, current_app.config["invocations"]["total"], current_app.config["invocations"]["perSecond"], range_list) - r = mycol.find_one({"service_id": service_id, "aef_id":aef_id, "apiInvokerPolicies.api_invoker_id": invoker_id}, {"_id":0}) + current_app.logger.info( + f"Adding invoker ACL for invoker {invoker_id}") + range_list = [TimeRangeList( + datetime.utcnow(), datetime.utcnow()+timedelta(days=365))] + invoker_acl = ApiInvokerPolicy( + invoker_id, current_app.config["invocations"]["total"], current_app.config["invocations"]["perSecond"], range_list) + r = mycol.find_one({"service_id": service_id, "aef_id": aef_id, + "apiInvokerPolicies.api_invoker_id": invoker_id}, {"_id": 0}) if r is None: - mycol.update_one({"service_id": service_id, "aef_id":aef_id }, {"$push":{"apiInvokerPolicies":invoker_acl.to_dict()}}) + mycol.update_one({"service_id": service_id, "aef_id": aef_id}, { + "$push": {"apiInvokerPolicies": invoker_acl.to_dict()}}) else: - current_app.logger.info(f"Creating service ACLs for service: {service_id}") - range_list = [TimeRangeList(datetime.utcnow(), datetime.utcnow()+timedelta(days=365))] - invoker_acl = ApiInvokerPolicy(invoker_id, current_app.config["invocations"]["total"], current_app.config["invocations"]["perSecond"], range_list) - - + current_app.logger.info( + f"Creating service ACLs for service: {service_id}") + range_list = [TimeRangeList( + datetime.utcnow(), datetime.utcnow()+timedelta(days=365))] + invoker_acl = ApiInvokerPolicy( + invoker_id, current_app.config["invocations"]["total"], current_app.config["invocations"]["perSecond"], range_list) service_acls = { "service_id": service_id, "aef_id": aef_id, - "apiInvokerPolicies": [invoker_acl.to_dict()] + "api_invoker_policies": [invoker_acl.to_dict()] } - mycol.insert_one(service_acls) - publisher_ops.publish_message("events", "ACCESS_CONTROL_POLICY_UPDATE") - - current_app.logger.info(f"Invoker ACL added for invoker: {invoker_id} for service: {service_id}") - + result = mycol.insert_one(service_acls) + + inserted_service_acls=mycol.find_one({"_id": result.inserted_id}, {"_id": 0}) + current_app.logger.info(inserted_service_acls) + inserted_service_acls_camel=dict_to_camel_case(inserted_service_acls) + current_app.logger.info(inserted_service_acls_camel) + accCtrlPolListExt = { + "apiId": service_id, + "apiInvokerPolicies": inserted_service_acls_camel['apiInvokerPolicies'] + } + RedisEvent("ACCESS_CONTROL_POLICY_UPDATE", + "accCtrlPolList", accCtrlPolListExt).send_event() + + current_app.logger.info( + f"Invoker ACL added for invoker: {invoker_id} for service: {service_id}") + def remove_acl(self, invoker_id, service_id, aef_id): current_app.logger.info(f"Removing ACL for invoker: {invoker_id}") - + mycol = self.db.get_col_by_name(self.db.acls) - res = mycol.find_one({"service_id": service_id, "aef_id":aef_id}, {"_id":0}) - + res = mycol.find_one( + {"service_id": service_id, "aef_id": aef_id}, {"_id": 0}) + if res: - mycol.update_many({"service_id": service_id, "aef_id":aef_id}, - {"$pull":{ "apiInvokerPolicies": { "api_invoker_id": invoker_id }}} - ) + mycol.update_many({"service_id": service_id, "aef_id": aef_id}, + {"$pull": {"api_invoker_policies": { + "api_invoker_id": invoker_id}}} + ) else: - current_app.logger.info(f"Not found: {service_id} for api : {service_id}") - - publisher_ops.publish_message("events", "ACCESS_CONTROL_POLICY_UNAVAILABLE") - - current_app.logger.info(f"Invoker ACL removed for invoker: {invoker_id} for service: {service_id}") - + current_app.logger.info( + f"Not found: {service_id} for api : {service_id}") + + RedisEvent("ACCESS_CONTROL_POLICY_UNAVAILABLE").send_event() + + current_app.logger.info( + f"Invoker ACL removed for invoker: {invoker_id} for service: {service_id}") + def remove_invoker_acl(self, invoker_id): current_app.logger.info(f"Removing ACLs for invoker: {invoker_id}") mycol = self.db.get_col_by_name(self.db.acls) - - mycol.update_many({"apiInvokerPolicies.api_invoker_id": invoker_id}, - {"$pull":{ "apiInvokerPolicies": { "api_invoker_id": invoker_id }}} - ) - publisher_ops.publish_message("events", "ACCESS_CONTROL_POLICY_UNAVAILABLE") + + mycol.update_many({"api_invoker_policies.api_invoker_id": invoker_id}, + {"$pull": {"api_invoker_policies": { + "api_invoker_id": invoker_id}}} + ) + RedisEvent("ACCESS_CONTROL_POLICY_UNAVAILABLE").send_event() current_app.logger.info(f"ACLs for invoker: {invoker_id} removed") - + def remove_provider_acls(self, id): current_app.logger.info(f"Removing ACLs for provider/service: {id}") mycol = self.db.get_col_by_name(self.db.acls) - - mycol.delete_many({"$or":[{"service_id":id}, {"aef_id":id}]} - ) - publisher_ops.publish_message("events", "ACCESS_CONTROL_POLICY_UNAVAILABLE") - current_app.logger.info(f"ACLs for provider/service: {id} removed") \ No newline at end of file + + mycol.delete_many({"$or": [{"service_id": id}, {"aef_id": id}]}) + RedisEvent("ACCESS_CONTROL_POLICY_UNAVAILABLE").send_event() + current_app.logger.info(f"ACLs for provider/service: {id} removed") diff --git a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/redis_event.py b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/redis_event.py new file mode 100644 index 0000000..da494eb --- /dev/null +++ b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/redis_event.py @@ -0,0 +1,41 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + + +class RedisEvent(): + def __init__(self, event, event_detail_key=None, information=None) -> None: + self.EVENTS_ENUM = [ + 'SERVICE_API_AVAILABLE', + 'SERVICE_API_UNAVAILABLE', + 'SERVICE_API_UPDATE', + 'API_INVOKER_ONBOARDED', + 'API_INVOKER_OFFBOARDED', + 'SERVICE_API_INVOCATION_SUCCESS', + 'SERVICE_API_INVOCATION_FAILURE', + 'ACCESS_CONTROL_POLICY_UPDATE', + 'ACCESS_CONTROL_POLICY_UNAVAILABLE', + 'API_INVOKER_AUTHORIZATION_REVOKED', + 'API_INVOKER_UPDATED', + 'API_TOPOLOGY_HIDING_CREATED', + 'API_TOPOLOGY_HIDING_REVOKED'] + if event not in self.EVENTS_ENUM: + raise Exception( + "Event (" + event + ") is not on event enum (" + ','.join(self.EVENTS_ENUM) + ")") + self.redis_event = { + "event": event + } + if event_detail_key != None and information != None: + self.redis_event['key'] = event_detail_key + self.redis_event['information'] = information + + def to_string(self): + return json.dumps(self.redis_event, cls=JSONEncoder) + + def send_event(self): + publisher_ops.publish_message("events-log", self.to_string()) + + def __call__(self): + return self.redis_event 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 e7b7dd5..0ece38e 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py @@ -45,7 +45,7 @@ class Notifications(): event_detail=None if redis_event.get('key', None) != None and redis_event.get('information', None) != None: event_detail={redis_event.get('key'):redis_event.get('information')} - + current_app.logger.debug(event_detail) data = EventNotification(sub["subscription_id"], events=redis_event.get('event'), event_detail=event_detail) current_app.logger.debug(json.dumps(data,cls=JSONEncoder)) diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index 8d6150e..1e86788 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -485,7 +485,112 @@ Provider subscribe to API Invoker events Check Variable ${events_expected} EventNotification # Check results - ${events_expected_length}= Get Length ${event_expected} + ${events_expected_length}= Get Length ${events_expected} + Length Should Be ${notification_events_on_mock_server} ${events_expected_length} + FOR ${event_expected} IN @{events_expected} + Log ${event_expected} + List Should Contain Value ${notification_events_on_mock_server} ${event_expected} + END + +Invoker subscribed to ACL update event + [Tags] capif_api_events-10 mockserver + + # Start Mock server + Check Mock Server + Clean Mock Server + + # Register APF + ${register_user_info_provider}= Provider Default Registration + + # Publish one api + ${service_api_description_published} ${resource_url} ${request_body}= Publish Service Api + ... ${register_user_info_provider} + + # Store apiId1 + ${serviceApiId}= Set Variable ${service_api_description_published['apiId']} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + # Subscribe to events + ${events_list}= Create List ACCESS_CONTROL_POLICY_UPDATE + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=${MOCK_SERVER_URL}/testing + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_provider['amf_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AMF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + + + # Test + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']}&aef-id=${register_user_info_provider['aef_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + Check Response Variable Type And Values ${discover_response} 200 DiscoveredAPIs + + # create Security Context + ${request_service_security_body}= Create Service Security From Discover Response + ... http://${CAPIF_HOSTNAME}:${CAPIF_HTTP_PORT}/test + ... ${discover_response} + ${resp}= Put Request Capif + ... /capif-security/v1/trustedInvokers/${register_user_info_invoker['api_invoker_id']} + ... json=${request_service_security_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Service Security + Check Response Variable Type And Values ${resp} 201 ServiceSecurity + ${resource_url}= Check Location Header ${resp} ${LOCATION_SECURITY_RESOURCE_REGEX} + + ${resp}= Get Request Capif + ... /access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${register_user_info_provider['aef_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + Check Response Variable Type And Values ${resp} 200 AccessControlPolicyList + # Check returned values + Should Not Be Empty ${resp.json()['apiInvokerPolicies']} + Length Should Be ${resp.json()['apiInvokerPolicies']} 1 + Should Be Equal As Strings + ... ${resp.json()['apiInvokerPolicies'][0]['apiInvokerId']} + ... ${register_user_info_invoker['api_invoker_id']} + + ${api_invoker_policies}= Set Variable ${resp.json()['apiInvokerPolicies']} + + # Check Results + Sleep 3s + # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. + ${resp}= Get Mock Server Messages + ${notification_events_on_mock_server}= Set Variable ${resp.json()} + + ## Create events expected + ${acc_ctrl_pol_list}= Create Dictionary apiId=${serviceApiId} apiInvokerPolicies=${api_invoker_policies} + Check Variable ${acc_ctrl_pol_list} AccessControlPolicyListExt + + ${events_expected}= Create List + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... ACCESS_CONTROL_POLICY_UPDATE + ... accCtrlPolList=${acc_ctrl_pol_list} + + Append To List ${events_expected} ${event_expected} + + Check Variable ${events_expected} EventNotification + + # Check results + ${events_expected_length}= Get Length ${events_expected} Length Should Be ${notification_events_on_mock_server} ${events_expected_length} FOR ${event_expected} IN @{events_expected} Log ${event_expected} diff --git a/tests/libraries/api_events/bodyRequests.py b/tests/libraries/api_events/bodyRequests.py index 14c8bb4..17fd1b5 100644 --- a/tests/libraries/api_events/bodyRequests.py +++ b/tests/libraries/api_events/bodyRequests.py @@ -1,33 +1,35 @@ -def create_events_subscription(events=["SERVICE_API_AVAILABLE", "API_INVOKER_ONBOARDED"],notificationDestination="http://robot.testing", eventFilters=None, eventReq=None, requestTestNotification=None, supportedFeatures=None,websockNotifConfig=None): - event_subscription={ +def create_events_subscription(events=["SERVICE_API_AVAILABLE", "API_INVOKER_ONBOARDED"], notificationDestination="http://robot.testing", eventFilters=None, eventReq=None, requestTestNotification=None, supportedFeatures=None, websockNotifConfig=None): + event_subscription = { "events": events, "notificationDestination": notificationDestination, } if eventFilters != None: - event_subscription['eventFilters']=eventFilters + event_subscription['eventFilters'] = eventFilters if eventReq != None: - event_subscription['eventReq']=eventReq + event_subscription['eventReq'] = eventReq if requestTestNotification != None: - event_subscription['requestTestNotification']=requestTestNotification + event_subscription['requestTestNotification'] = requestTestNotification if supportedFeatures != None: - event_subscription['supportedFeatures']=supportedFeatures + event_subscription['supportedFeatures'] = supportedFeatures if websockNotifConfig != None: - event_subscription['websockNotifConfig']=websockNotifConfig - + event_subscription['websockNotifConfig'] = websockNotifConfig + return event_subscription + def create_capif_event_filter(aefIds=None, apiIds=None, apiInvokerIds=None): if aefIds == None and apiIds == None and apiInvokerIds: - raise("Error, no data present to create event filter") - capif_event_filter=dict() + raise ("Error, no data present to create event filter") + capif_event_filter = dict() if aefIds != None: - capif_event_filter['aefIds']=aefIds + capif_event_filter['aefIds'] = aefIds if apiIds != None: - capif_event_filter['apiIds']=apiIds + capif_event_filter['apiIds'] = apiIds if apiInvokerIds != None: - capif_event_filter['apiInvokerIds']=apiInvokerIds + capif_event_filter['apiInvokerIds'] = apiInvokerIds return capif_event_filter + def create_default_event_req(): return { "grpRepTime": 5, @@ -39,55 +41,55 @@ def create_default_event_req(): "sampRatio": 15 } + def create_websock_notif_config_default(): return { "requestWebsocketUri": True, "websocketUri": "websocketUri" } + def create_notification_event(subscriptionId, event, serviceAPIDescriptions=None, apiIds=None, apiInvokerIds=None, accCtrlPolList=None, invocationLogs=None, apiTopoHide=None): - result={ - "subscriptionId":subscriptionId, + result = { + "subscriptionId": subscriptionId, "events": event, "eventDetail": dict() } - count=0 + count = 0 if serviceAPIDescriptions != None: - if isinstance(serviceAPIDescriptions,list): - result['eventDetail']['serviceAPIDescriptions']=serviceAPIDescriptions + if isinstance(serviceAPIDescriptions, list): + result['eventDetail']['serviceAPIDescriptions'] = serviceAPIDescriptions else: - result['eventDetail']['serviceAPIDescriptions']=[serviceAPIDescriptions] - count=count+1 + result['eventDetail']['serviceAPIDescriptions'] = [ + serviceAPIDescriptions] + count = count+1 if apiIds != None: - if isinstance(apiIds,list): - result['eventDetail']['apiIds']=apiIds + if isinstance(apiIds, list): + result['eventDetail']['apiIds'] = apiIds else: - result['eventDetail']['apiIds']=[apiIds] - count=count+1 + result['eventDetail']['apiIds'] = [apiIds] + count = count+1 if apiInvokerIds != None: - if isinstance(apiInvokerIds,list): - result['eventDetail']['apiInvokerIds']=apiInvokerIds + if isinstance(apiInvokerIds, list): + result['eventDetail']['apiInvokerIds'] = apiInvokerIds else: - result['eventDetail']['apiInvokerIds']=[apiInvokerIds] - count=count+1 + result['eventDetail']['apiInvokerIds'] = [apiInvokerIds] + count = count+1 if accCtrlPolList != None: - if isinstance(accCtrlPolList,list): - result['eventDetail']['accCtrlPolList']=accCtrlPolList - else: - result['eventDetail']['accCtrlPolList']=[accCtrlPolList] - count=count+1 + result['eventDetail']['accCtrlPolList'] = accCtrlPolList + count = count+1 if invocationLogs != None: - if isinstance(invocationLogs,list): - result['eventDetail']['invocationLogs']=invocationLogs + if isinstance(invocationLogs, list): + result['eventDetail']['invocationLogs'] = invocationLogs else: - result['eventDetail']['invocationLogs']=[invocationLogs] - count=count+1 + result['eventDetail']['invocationLogs'] = [invocationLogs] + count = count+1 if apiTopoHide != None: - if isinstance(apiTopoHide,list): - result['eventDetail']['apiTopoHide']=apiTopoHide + if isinstance(apiTopoHide, list): + result['eventDetail']['apiTopoHide'] = apiTopoHide else: - result['eventDetail']['apiTopoHide']=[apiTopoHide] - count=count+1 + result['eventDetail']['apiTopoHide'] = [apiTopoHide] + count = count+1 if count == 0: del result['eventDetail'] -- GitLab From 62a965e035c01490377a612caf9eb5dcb789c82f Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Tue, 28 May 2024 12:08:53 +0200 Subject: [PATCH 16/26] Fixed issue storing data with camelcase mixed with snake case --- .../openapi_server/core/accesscontrolpolicyapi.py | 4 ++-- .../openapi_server/core/internal_service_ops.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/accesscontrolpolicyapi.py b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/accesscontrolpolicyapi.py index b5698be..f644cae 100644 --- a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/accesscontrolpolicyapi.py +++ b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/accesscontrolpolicyapi.py @@ -20,8 +20,8 @@ class accessControlPolicyApi(Resource): projection = {"_id":0} if api_invoker_id is not None: - query['apiInvokerPolicies.api_invoker_id'] = api_invoker_id - projection['apiInvokerPolicies.$'] = 1 + query['api_invoker_policies.api_invoker_id'] = api_invoker_id + projection['api_invoker_policies.$'] = 1 if supported_features is not None: current_app.logger.debug(f"SupportedFeatures present on query with value {supported_features}, but currently not used") diff --git a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/internal_service_ops.py b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/internal_service_ops.py index 8d6a66c..26a7d53 100644 --- a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/internal_service_ops.py +++ b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/internal_service_ops.py @@ -30,10 +30,10 @@ class InternalServiceOps(Resource): invoker_acl = ApiInvokerPolicy( invoker_id, current_app.config["invocations"]["total"], current_app.config["invocations"]["perSecond"], range_list) r = mycol.find_one({"service_id": service_id, "aef_id": aef_id, - "apiInvokerPolicies.api_invoker_id": invoker_id}, {"_id": 0}) + "api_invoker_policies.api_invoker_id": invoker_id}, {"_id": 0}) if r is None: mycol.update_one({"service_id": service_id, "aef_id": aef_id}, { - "$push": {"apiInvokerPolicies": invoker_acl.to_dict()}}) + "$push": {"api_invoker_policies": invoker_acl.to_dict()}}) else: current_app.logger.info( f"Creating service ACLs for service: {service_id}") -- GitLab From 6b59d7fa18c150680c9e872eae8a1e7479a16a42 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Tue, 28 May 2024 12:11:07 +0200 Subject: [PATCH 17/26] Code Format and Code refactor at Security Service --- .../controllers/default_controller.py | 5 +- .../capif_security/core/servicesecurity.py | 193 +++++++++++------- 2 files changed, 122 insertions(+), 76 deletions(-) diff --git a/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py b/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py index aee7098..e37ecde 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py @@ -117,10 +117,7 @@ def trusted_invokers_api_invoker_id_delete_post(api_invoker_id, body): # noqa: current_app.logger.info("Revoking permissions") res = service_security_ops.revoke_api_authorization(api_invoker_id, body) - if res.status_code == 204: - current_app.logger.info("Permissions revoked") - publish_ops.publish_message("events", "API_INVOKER_AUTHORIZATION_REVOKED") - + return res @cert_validation() diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/servicesecurity.py b/services/TS29222_CAPIF_Security_API/capif_security/core/servicesecurity.py index 69475ac..8f48465 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/core/servicesecurity.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/servicesecurity.py @@ -25,19 +25,22 @@ from .responses import not_found_error, make_response, bad_request_error, intern from .notification import Notifications from .resources import Resource import os +from .redis_event import RedisEvent publish_ops = Publisher() security_context_not_found_detail = "Security context not found" api_invoker_no_context_cause = "API Invoker has no security context" + class SecurityOperations(Resource): def __check_invoker(self, api_invoker_id): invokers_col = self.db.get_col_by_name(self.db.capif_invokers) - current_app.logger.debug("Checking api invoker with id: " + api_invoker_id) - invoker = invokers_col.find_one({"api_invoker_id": api_invoker_id}) + current_app.logger.debug( + "Checking api invoker with id: " + api_invoker_id) + invoker = invokers_col.find_one({"api_invoker_id": api_invoker_id}) if invoker is None: current_app.logger.error("Invoker not found") return not_found_error(detail="Invoker not found", cause="API Invoker not exists or invalid ID") @@ -52,12 +55,14 @@ class SecurityOperations(Resource): header = scope[0:4] if header != "3gpp": current_app.logger.error("Bad format scope") - token_error = AccessTokenErr(error="invalid_scope", error_description="The first characters must be '3gpp'") + token_error = AccessTokenErr( + error="invalid_scope", error_description="The first characters must be '3gpp'") return make_response(object=token_error, status=400) _, body = scope.split("#") - capif_service_col = self.db.get_col_by_name(self.db.capif_service_col) + capif_service_col = self.db.get_col_by_name( + self.db.capif_service_col) security_info = security_context["security_info"] aef_security_context = [info["aef_id"] for info in security_info] @@ -65,22 +70,28 @@ class SecurityOperations(Resource): for group in groups: aef_id, api_names = group.split(":") if aef_id not in aef_security_context: - current_app.logger.error("Bad format Scope, not valid aef id ") - token_error = AccessTokenErr(error="invalid_scope", error_description="One of aef_id not belongs of your security context") + current_app.logger.error( + "Bad format Scope, not valid aef id ") + token_error = AccessTokenErr( + error="invalid_scope", error_description="One of aef_id not belongs of your security context") return make_response(object=token_error, status=400) api_names = api_names.split(",") for api_name in api_names: - service = capif_service_col.find_one({"$and": [{"api_name":api_name},{self.filter_aef_id:aef_id}]}) + service = capif_service_col.find_one( + {"$and": [{"api_name": api_name}, {self.filter_aef_id: aef_id}]}) if service is None: - current_app.logger.error("Bad format Scope, not valid api name") - token_error = AccessTokenErr(error="invalid_scope", error_description="One of the api names does not exist or is not associated with the aef id provided") + current_app.logger.error( + "Bad format Scope, not valid api name") + token_error = AccessTokenErr( + error="invalid_scope", error_description="One of the api names does not exist or is not associated with the aef id provided") return make_response(object=token_error, status=400) return None except Exception as e: current_app.logger.error("Bad format Scope: " + e) - token_error = AccessTokenErr(error="invalid_scope", error_description="malformed scope") + token_error = AccessTokenErr( + error="invalid_scope", error_description="malformed scope") return make_response(object=token_error, status=400) def __init__(self): @@ -93,16 +104,18 @@ class SecurityOperations(Resource): try: - current_app.logger.debug("Obtainig security context with id: " + api_invoker_id) + current_app.logger.debug( + "Obtainig security context with id: " + api_invoker_id) result = self.__check_invoker(api_invoker_id) if result != None: return result else: - services_security_object = mycol.find_one({"api_invoker_id": api_invoker_id}, {"_id":0, "api_invoker_id":0}) + services_security_object = mycol.find_one({"api_invoker_id": api_invoker_id}, { + "_id": 0, "api_invoker_id": 0}) if services_security_object is None: current_app.logger.error("Not found security context") - return not_found_error(detail= security_context_not_found_detail, cause=api_invoker_no_context_cause) + return not_found_error(detail=security_context_not_found_detail, cause=api_invoker_no_context_cause) if not authentication_info: for security_info_obj in services_security_object['security_info']: @@ -111,12 +124,15 @@ class SecurityOperations(Resource): for security_info_obj in services_security_object['security_info']: del security_info_obj['authorization_info'] - properyly_json= json.dumps(services_security_object, default=json_util.default) - my_service_security = dict_to_camel_case(json.loads(properyly_json)) + properyly_json = json.dumps( + services_security_object, default=json_util.default) + my_service_security = dict_to_camel_case( + json.loads(properyly_json)) my_service_security = clean_empty(my_service_security) - current_app.logger.debug("Obtained security context from database") - + current_app.logger.debug( + "Obtained security context from database") + res = make_response(object=my_service_security, status=200) return res @@ -125,7 +141,6 @@ class SecurityOperations(Resource): current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - def create_servicesecurity(self, api_invoker_id, service_security): mycol = self.db.get_col_by_name(self.db.security_info) @@ -139,43 +154,54 @@ class SecurityOperations(Resource): if rfc3987.match(service_security.notification_destination, rule="URI") is None: current_app.logger.error("Bad url format") - return bad_request_error(detail="Bad Param", cause = "Detected Bad format of param", invalid_params=[{"param": "notificationDestination", "reason": "Not valid URL format"}]) + return bad_request_error(detail="Bad Param", cause="Detected Bad format of param", invalid_params=[{"param": "notificationDestination", "reason": "Not valid URL format"}]) - services_security_object = mycol.find_one({"api_invoker_id": api_invoker_id}) + services_security_object = mycol.find_one( + {"api_invoker_id": api_invoker_id}) if services_security_object is not None: - current_app.logger.error("Already security context defined with same api invoker id") + current_app.logger.error( + "Already security context defined with same api invoker id") return forbidden_error(detail="Security method already defined", cause="Identical AEF Profile IDs") - for service_instance in service_security.security_info: if service_instance.interface_details is not None: security_methods = service_instance.interface_details.security_methods pref_security_methods = service_instance.pref_security_methods - valid_security_method = set(security_methods) & set(pref_security_methods) + valid_security_method = set( + security_methods) & set(pref_security_methods) else: - capif_service_col = self.db.get_col_by_name(self.db.capif_service_col) - services_security_object = capif_service_col.find_one({"api_id":service_instance.api_id, self.filter_aef_id: service_instance.aef_id}, {"aef_profiles.security_methods.$":1}) + capif_service_col = self.db.get_col_by_name( + self.db.capif_service_col) + services_security_object = capif_service_col.find_one( + {"api_id": service_instance.api_id, self.filter_aef_id: service_instance.aef_id}, {"aef_profiles.security_methods.$": 1}) if services_security_object is None: - current_app.logger.error("Not found service with this aef id: " + service_instance.aef_id) + current_app.logger.error( + "Not found service with this aef id: " + service_instance.aef_id) return not_found_error(detail="Service with this aefId not found", cause="Not found Service") pref_security_methods = service_instance.pref_security_methods - valid_security_methods = [security_method for array_methods in services_security_object["aef_profiles"] for security_method in array_methods["security_methods"]] - valid_security_method = set(valid_security_methods) & set(pref_security_methods) + valid_security_methods = [security_method for array_methods in services_security_object["aef_profiles"] + for security_method in array_methods["security_methods"]] + valid_security_method = set( + valid_security_methods) & set(pref_security_methods) if len(list(valid_security_method)) == 0: - current_app.logger.error("Not found comptaible security method with pref security method") + current_app.logger.error( + "Not found comptaible security method with pref security method") return bad_request_error(detail="Not found compatible security method with pref security method", cause="Error pref security method", invalid_params=[{"param": "prefSecurityMethods", "reason": "pref security method not compatible with security method available"}]) - service_instance.sel_security_method = list(valid_security_method)[0] + service_instance.sel_security_method = list( + valid_security_method)[0] # Send service instance to ACL current_app.logger.debug("Sending message to create ACL") - publish_ops.publish_message("acls-messages", "create-acl:"+str(api_invoker_id)+":"+str(service_instance.api_id)+":"+str(service_instance.aef_id)) - current_app.logger.debug("Inserted security context in database") + publish_ops.publish_message("acls-messages", "create-acl:"+str( + api_invoker_id)+":"+str(service_instance.api_id)+":"+str(service_instance.aef_id)) + current_app.logger.debug( + "Inserted security context in database") rec = dict() rec['api_invoker_id'] = api_invoker_id @@ -183,7 +209,8 @@ class SecurityOperations(Resource): mycol.insert_one(rec) res = make_response(object=service_security, status=201) - res.headers['Location'] = "https://{}/capif-security/v1/trustedInvokers/{}".format(os.getenv('CAPIF_HOSTNAME'),str(api_invoker_id)) + res.headers['Location'] = "https://{}/capif-security/v1/trustedInvokers/{}".format( + os.getenv('CAPIF_HOSTNAME'), str(api_invoker_id)) return res except Exception as e: @@ -191,7 +218,6 @@ class SecurityOperations(Resource): current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - def delete_servicesecurity(self, api_invoker_id): mycol = self.db.get_col_by_name(self.db.security_info) @@ -210,19 +236,22 @@ class SecurityOperations(Resource): if services_security_count == 0: current_app.logger.error(security_context_not_found_detail) return not_found_error(detail=security_context_not_found_detail, cause=api_invoker_no_context_cause) - + mycol.delete_many(my_query) - publish_ops.publish_message("acls-messages", "remove-acl:"+api_invoker_id) + publish_ops.publish_message( + "acls-messages", "remove-acl:"+api_invoker_id) - current_app.logger.debug("Removed security context from database") - out= "The security info of Netapp with Netapp ID " + api_invoker_id + " were deleted.", 204 + current_app.logger.debug( + "Removed security context from database") + out = "The security info of Netapp with Netapp ID " + \ + api_invoker_id + " were deleted.", 204 return make_response(out, status=204) except Exception as e: exception = "An exception occurred in create security info" 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 delete_intern_servicesecurity(self, api_invoker_id): @@ -240,34 +269,41 @@ class SecurityOperations(Resource): invokers_col = self.db.get_col_by_name(self.db.capif_invokers) - current_app.logger.debug("Checking api invoker with id: " + access_token_req["client_id"]) - invoker = invokers_col.find_one({"api_invoker_id": access_token_req["client_id"]}) + current_app.logger.debug( + "Checking api invoker with id: " + access_token_req["client_id"]) + invoker = invokers_col.find_one( + {"api_invoker_id": access_token_req["client_id"]}) if invoker is None: - client_id_error = AccessTokenErr(error="invalid_client", error_description="Client Id not found") + client_id_error = AccessTokenErr( + error="invalid_client", error_description="Client Id not found") return make_response(object=client_id_error, status=400) - if access_token_req["grant_type"] != "client_credentials": - client_id_error = AccessTokenErr(error="unsupported_grant_type", error_description="Invalid value for `grant_type` ({0}), must be one of ['client_credentials'] - 'grant_type'" - .format(access_token_req["grant_type"])) + client_id_error = AccessTokenErr(error="unsupported_grant_type", error_description="Invalid value for `grant_type` ({0}), must be one of ['client_credentials'] - 'grant_type'" + .format(access_token_req["grant_type"])) return make_response(object=client_id_error, status=400) service_security = mycol.find_one({"api_invoker_id": security_id}) if service_security is None: - current_app.logger.error("Not found securoty context with id: " + security_id) - return not_found_error(detail= security_context_not_found_detail, cause=api_invoker_no_context_cause) + current_app.logger.error( + "Not found security context with id: " + security_id) + return not_found_error(detail=security_context_not_found_detail, cause=api_invoker_no_context_cause) - result = self.__check_scope(access_token_req["scope"], service_security) + result = self.__check_scope( + access_token_req["scope"], service_security) if result != None: return result expire_time = timedelta(minutes=10) - now=datetime.now() + now = datetime.now() - claims = AccessTokenClaims(iss = access_token_req["client_id"], scope=access_token_req["scope"], exp=int((now+expire_time).timestamp())) - access_token = create_access_token(identity = access_token_req["client_id"] , additional_claims=claims.to_dict()) - access_token_resp = AccessTokenRsp(access_token=access_token, token_type="Bearer", expires_in=int(expire_time.total_seconds()), scope=access_token_req["scope"]) + claims = AccessTokenClaims(iss=access_token_req["client_id"], scope=access_token_req["scope"], exp=int( + (now+expire_time).timestamp())) + access_token = create_access_token( + identity=access_token_req["client_id"], additional_claims=claims.to_dict()) + access_token_resp = AccessTokenRsp(access_token=access_token, token_type="Bearer", expires_in=int( + expire_time.total_seconds()), scope=access_token_req["scope"]) current_app.logger.debug("Created access token") @@ -278,7 +314,6 @@ class SecurityOperations(Resource): current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - def update_servicesecurity(self, api_invoker_id, service_security): mycol = self.db.get_col_by_name(self.db.security_info) try: @@ -291,38 +326,48 @@ class SecurityOperations(Resource): old_object = mycol.find_one({"api_invoker_id": api_invoker_id}) if old_object is None: - current_app.logger.error("Service api not found with id: " + api_invoker_id) + current_app.logger.error( + "Service api not found with id: " + api_invoker_id) return not_found_error(detail="Service API not existing", cause="Not exist securiy information for this invoker") for service_instance in service_security.security_info: if service_instance.interface_details is not None: security_methods = service_instance.interface_details.security_methods pref_security_methods = service_instance.pref_security_methods - valid_security_method = set(security_methods) & set(pref_security_methods) - service_instance.sel_security_method = list(valid_security_method)[0] + valid_security_method = set( + security_methods) & set(pref_security_methods) + service_instance.sel_security_method = list( + valid_security_method)[0] else: - capif_service_col = self.db.get_col_by_name(self.db.capif_service_col) - services_security_object = capif_service_col.find_one({self.filter_aef_id: service_instance.aef_id}, {"aef_profiles.security_methods.$":1}) + capif_service_col = self.db.get_col_by_name( + self.db.capif_service_col) + services_security_object = capif_service_col.find_one( + {self.filter_aef_id: service_instance.aef_id}, {"aef_profiles.security_methods.$": 1}) if services_security_object is None: - current_app.logger.error("Service api with this aefId not found: " + service_instance.aef_id) + current_app.logger.error( + "Service api with this aefId not found: " + service_instance.aef_id) return not_found_error(detail="Service with this aefId not found", cause="Not found Service") pref_security_methods = service_instance.pref_security_methods - valid_security_methods = [security_method for array_methods in services_security_object["aef_profiles"] for security_method in array_methods["security_methods"]] - valid_security_method = set(valid_security_methods) & set(pref_security_methods) - service_instance.sel_security_method = list(valid_security_method)[0] + valid_security_methods = [security_method for array_methods in services_security_object["aef_profiles"] + for security_method in array_methods["security_methods"]] + valid_security_method = set( + valid_security_methods) & set(pref_security_methods) + service_instance.sel_security_method = list( + valid_security_method)[0] service_security = service_security.to_dict() service_security = clean_empty(service_security) - result = mycol.find_one_and_update(old_object, {"$set":service_security}, projection={'_id': 0, "api_invoker_id":0},return_document=ReturnDocument.AFTER ,upsert=False) + result = mycol.find_one_and_update(old_object, {"$set": service_security}, projection={ + '_id': 0, "api_invoker_id": 0}, return_document=ReturnDocument.AFTER, upsert=False) result = clean_empty(result) current_app.logger.debug("Updated security context") - res= make_response(object=dict_to_camel_case(result), status=200) + res = make_response(object=dict_to_camel_case(result), status=200) res.headers['Location'] = "https://${CAPIF_HOSTNAME}/capif-security/v1/trustedInvokers/" + str( api_invoker_id) return res @@ -331,7 +376,6 @@ class SecurityOperations(Resource): current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - def revoke_api_authorization(self, api_invoker_id, security_notification): mycol = self.db.get_col_by_name(self.db.security_info) @@ -352,10 +396,12 @@ class SecurityOperations(Resource): updated_security_context = services_security_context.copy() for context in services_security_context["security_info"]: - index = services_security_context["security_info"].index(context) + index = services_security_context["security_info"].index( + context) if security_notification.aef_id == context["aef_id"] or context["api_id"] in security_notification.api_ids: current_app.logger.debug("Sending message.") - publish_ops.publish_message("acls-messages", "remove-acl:"+str(api_invoker_id)+":"+str(context["api_id"])+":"+str(security_notification.aef_id)) + publish_ops.publish_message("acls-messages", "remove-acl:"+str( + api_invoker_id)+":"+str(context["api_id"])+":"+str(security_notification.aef_id)) current_app.logger.debug("message sended.") updated_security_context["security_info"].pop(index) @@ -364,13 +410,16 @@ class SecurityOperations(Resource): if len(updated_security_context["security_info"]) == 0: mycol.delete_many(my_query) - #self.notification.send_notification(services_security_context["notification_destination"], security_notification) - current_app.logger.debug("Revoked security context") - out= "Netapp with ID " + api_invoker_id + " was revoked by some APIs.", 204 - return make_response(out, status=204) + out = "Netapp with ID " + api_invoker_id + " was revoked by some APIs.", 204 + res = make_response(out, status=204) + if res.status_code == 204: + current_app.logger.info("Permissions revoked") + RedisEvent("API_INVOKER_AUTHORIZATION_REVOKED").send_event() + + return res except Exception as e: exception = "An exception occurred in revoke security auth" current_app.logger.error(exception + "::" + str(e)) - return internal_server_error(detail=exception, cause=str(e)) \ No newline at end of file + return internal_server_error(detail=exception, cause=str(e)) -- GitLab From 62a582012f034097a45ca3ff166b00a3b9548134 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Tue, 28 May 2024 12:13:28 +0200 Subject: [PATCH 18/26] Added test 11 and 12 at Events suite --- .../capif_api_access_control_policy.robot | 1 - .../CAPIF Api Events/capif_events_api.robot | 195 ++++++++++++++++++ tests/resources/common.resource | 1 + 3 files changed, 196 insertions(+), 1 deletion(-) diff --git a/tests/features/CAPIF Api Access Control Policy/capif_api_access_control_policy.robot b/tests/features/CAPIF Api Access Control Policy/capif_api_access_control_policy.robot index 04d7898..ec0f082 100644 --- a/tests/features/CAPIF Api Access Control Policy/capif_api_access_control_policy.robot +++ b/tests/features/CAPIF Api Access Control Policy/capif_api_access_control_policy.robot @@ -238,7 +238,6 @@ Retrieve ACL with security context created by two different Invokers ... username=${AEF_PROVIDER_USERNAME} Check Response Variable Type And Values ${resp} 200 AccessControlPolicyList - # Check returned values Should Not Be Empty ${resp.json()['apiInvokerPolicies']} Length Should Be ${resp.json()['apiInvokerPolicies']} 2 diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index 1e86788..950cdad 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -597,3 +597,198 @@ Invoker subscribed to ACL update event List Should Contain Value ${notification_events_on_mock_server} ${event_expected} END +Provider receives an ACL unavailable event when invoker remove Security Context. + [Tags] capif_api_events-11 mockserver + + # Start Mock server + Check Mock Server + Clean Mock Server + + # Register APF + ${register_user_info_provider}= Provider Default Registration + + # Publish one api + ${service_api_description_published} ${resource_url} ${request_body}= Publish Service Api + ... ${register_user_info_provider} + + # Store apiId1 + ${serviceApiId}= Set Variable ${service_api_description_published['apiId']} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + # Subscribe to events + ${events_list}= Create List ACCESS_CONTROL_POLICY_UNAVAILABLE + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=${MOCK_SERVER_URL}/testing + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_provider['amf_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + + + # Test + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']}&aef-id=${register_user_info_provider['aef_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + Check Response Variable Type And Values ${discover_response} 200 DiscoveredAPIs + + # create Security Context + ${request_service_security_body}= Create Service Security From Discover Response + ... http://${CAPIF_HOSTNAME}:${CAPIF_HTTP_PORT}/test + ... ${discover_response} + ${resp}= Put Request Capif + ... /capif-security/v1/trustedInvokers/${register_user_info_invoker['api_invoker_id']} + ... json=${request_service_security_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Service Security + Check Response Variable Type And Values ${resp} 201 ServiceSecurity + ${resource_url}= Check Location Header ${resp} ${LOCATION_SECURITY_RESOURCE_REGEX} + + # Remove Security Context by Provider + ${resp}= Delete Request Capif + ... /capif-security/v1/trustedInvokers/${register_user_info_invoker['api_invoker_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + Status Should Be 204 ${resp} + + # Check Results + Sleep 3s + # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. + ${resp}= Get Mock Server Messages + ${notification_events_on_mock_server}= Set Variable ${resp.json()} + + ## Create events expected + ${events_expected}= Create List + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... ACCESS_CONTROL_POLICY_UNAVAILABLE + Append To List ${events_expected} ${event_expected} + Check Variable ${events_expected} EventNotification + + # Check results + ${events_expected_length}= Get Length ${events_expected} + Length Should Be ${notification_events_on_mock_server} ${events_expected_length} + FOR ${event_expected} IN @{events_expected} + Log ${event_expected} + List Should Contain Value ${notification_events_on_mock_server} ${event_expected} + END + +Invoker receives an Invoker Authorization Revoked and ACL unavailable event when Provider revoke Invoker Authorization. + [Tags] capif_api_events-12 mockserver + + # Start Mock server + Check Mock Server + Clean Mock Server + + # Register APF + ${register_user_info_provider}= Provider Default Registration + + # Publish one api + ${service_api_description_published} ${resource_url} ${request_body}= Publish Service Api + ... ${register_user_info_provider} + + # Store apiId1 + ${serviceApiId}= Set Variable ${service_api_description_published['apiId']} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + # Subscribe to events + ${events_list}= Create List ACCESS_CONTROL_POLICY_UNAVAILABLE API_INVOKER_AUTHORIZATION_REVOKED + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=${MOCK_SERVER_URL}/testing + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_provider['amf_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + + # Test + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']}&aef-id=${register_user_info_provider['aef_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + Check Response Variable Type And Values ${discover_response} 200 DiscoveredAPIs + + ${api_ids}= Get Api Ids From Discover Response ${discover_response} + + # create Security Context + ${request_service_security_body}= Create Service Security From Discover Response + ... http://${CAPIF_HOSTNAME}:${CAPIF_HTTP_PORT}/test + ... ${discover_response} + ${resp}= Put Request Capif + ... /capif-security/v1/trustedInvokers/${register_user_info_invoker['api_invoker_id']} + ... json=${request_service_security_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Service Security + Check Response Variable Type And Values ${resp} 201 ServiceSecurity + ${resource_url}= Check Location Header ${resp} ${LOCATION_SECURITY_RESOURCE_REGEX} + + # Revoke Security Context by Provider + ${request_body}= Create Security Notification Body + ... ${register_user_info_invoker['api_invoker_id']} + ... ${api_ids} + ${resp}= Post Request Capif + ... /capif-security/v1/trustedInvokers/${register_user_info_invoker['api_invoker_id']}/delete + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + # Check Results + Status Should Be 204 ${resp} + + # Check Results + Sleep 3s + # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. + ${resp}= Get Mock Server Messages + ${notification_events_on_mock_server}= Set Variable ${resp.json()} + + ## Create events expected + ${events_expected}= Create List + ## ACCESS_CONTROL_POLICY_UNAVAILABLE event + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... ACCESS_CONTROL_POLICY_UNAVAILABLE + Append To List ${events_expected} ${event_expected} + ## API_INVOKER_AUTHORIZATION_REVOKED event + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... API_INVOKER_AUTHORIZATION_REVOKED + Append To List ${events_expected} ${event_expected} + Check Variable ${events_expected} EventNotification + + # Check results + ${events_expected_length}= Get Length ${events_expected} + Length Should Be ${notification_events_on_mock_server} ${events_expected_length} + FOR ${event_expected} IN @{events_expected} + Log ${event_expected} + List Should Contain Value ${notification_events_on_mock_server} ${event_expected} + END \ No newline at end of file diff --git a/tests/resources/common.resource b/tests/resources/common.resource index 9521310..d02891d 100644 --- a/tests/resources/common.resource +++ b/tests/resources/common.resource @@ -160,4 +160,5 @@ Get Mock Server Messages ... verify=False Status Should Be 200 ${resp} + Log List ${resp.json()} RETURN ${resp} \ No newline at end of file -- GitLab From dece008c92c4685212f9dbfaaf305d019ca2b87f Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Tue, 28 May 2024 12:14:07 +0200 Subject: [PATCH 19/26] Added RedisEvent class to Security Service --- .../capif_security/core/redis_event.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 services/TS29222_CAPIF_Security_API/capif_security/core/redis_event.py diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/redis_event.py b/services/TS29222_CAPIF_Security_API/capif_security/core/redis_event.py new file mode 100644 index 0000000..da494eb --- /dev/null +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/redis_event.py @@ -0,0 +1,41 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + + +class RedisEvent(): + def __init__(self, event, event_detail_key=None, information=None) -> None: + self.EVENTS_ENUM = [ + 'SERVICE_API_AVAILABLE', + 'SERVICE_API_UNAVAILABLE', + 'SERVICE_API_UPDATE', + 'API_INVOKER_ONBOARDED', + 'API_INVOKER_OFFBOARDED', + 'SERVICE_API_INVOCATION_SUCCESS', + 'SERVICE_API_INVOCATION_FAILURE', + 'ACCESS_CONTROL_POLICY_UPDATE', + 'ACCESS_CONTROL_POLICY_UNAVAILABLE', + 'API_INVOKER_AUTHORIZATION_REVOKED', + 'API_INVOKER_UPDATED', + 'API_TOPOLOGY_HIDING_CREATED', + 'API_TOPOLOGY_HIDING_REVOKED'] + if event not in self.EVENTS_ENUM: + raise Exception( + "Event (" + event + ") is not on event enum (" + ','.join(self.EVENTS_ENUM) + ")") + self.redis_event = { + "event": event + } + if event_detail_key != None and information != None: + self.redis_event['key'] = event_detail_key + self.redis_event['information'] = information + + def to_string(self): + return json.dumps(self.redis_event, cls=JSONEncoder) + + def send_event(self): + publisher_ops.publish_message("events-log", self.to_string()) + + def __call__(self): + return self.redis_event -- GitLab From 45bd21d68284f1839340c16048bc707a2d2b836a Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Tue, 28 May 2024 12:27:13 +0200 Subject: [PATCH 20/26] Setup common RedisEvent object to all services that sends events to Event Service --- .../api_invoker_management/core/redis_event.py | 9 +++++---- .../api_invocation_logs/core/redis_event.py | 9 +++++---- .../published_apis/core/redis_event.py | 9 +++++---- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_event.py b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_event.py index 037eade..da494eb 100644 --- a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_event.py +++ b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_event.py @@ -6,7 +6,7 @@ publisher_ops = Publisher() class RedisEvent(): - def __init__(self, event, event_detail_key, information) -> None: + def __init__(self, event, event_detail_key=None, information=None) -> None: self.EVENTS_ENUM = [ 'SERVICE_API_AVAILABLE', 'SERVICE_API_UNAVAILABLE', @@ -25,10 +25,11 @@ class RedisEvent(): raise Exception( "Event (" + event + ") is not on event enum (" + ','.join(self.EVENTS_ENUM) + ")") self.redis_event = { - "event": event, - "key": event_detail_key, - "information": information + "event": event } + if event_detail_key != None and information != None: + self.redis_event['key'] = event_detail_key + self.redis_event['information'] = information def to_string(self): return json.dumps(self.redis_event, cls=JSONEncoder) diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py index 037eade..da494eb 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py @@ -6,7 +6,7 @@ publisher_ops = Publisher() class RedisEvent(): - def __init__(self, event, event_detail_key, information) -> None: + def __init__(self, event, event_detail_key=None, information=None) -> None: self.EVENTS_ENUM = [ 'SERVICE_API_AVAILABLE', 'SERVICE_API_UNAVAILABLE', @@ -25,10 +25,11 @@ class RedisEvent(): raise Exception( "Event (" + event + ") is not on event enum (" + ','.join(self.EVENTS_ENUM) + ")") self.redis_event = { - "event": event, - "key": event_detail_key, - "information": information + "event": event } + if event_detail_key != None and information != None: + self.redis_event['key'] = event_detail_key + self.redis_event['information'] = information def to_string(self): return json.dumps(self.redis_event, cls=JSONEncoder) diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/redis_event.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/redis_event.py index 037eade..da494eb 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/redis_event.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/redis_event.py @@ -6,7 +6,7 @@ publisher_ops = Publisher() class RedisEvent(): - def __init__(self, event, event_detail_key, information) -> None: + def __init__(self, event, event_detail_key=None, information=None) -> None: self.EVENTS_ENUM = [ 'SERVICE_API_AVAILABLE', 'SERVICE_API_UNAVAILABLE', @@ -25,10 +25,11 @@ class RedisEvent(): raise Exception( "Event (" + event + ") is not on event enum (" + ','.join(self.EVENTS_ENUM) + ")") self.redis_event = { - "event": event, - "key": event_detail_key, - "information": information + "event": event } + if event_detail_key != None and information != None: + self.redis_event['key'] = event_detail_key + self.redis_event['information'] = information def to_string(self): return json.dumps(self.redis_event, cls=JSONEncoder) -- GitLab From efc75241f990c61d00b593714903a6e06133786c Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Tue, 28 May 2024 16:25:32 +0200 Subject: [PATCH 21/26] Change redis bus name to events, also code refactor of events --- .../core/redis_event.py | 2 +- .../openapi_server/core/redis_event.py | 2 +- .../capif_events/core/consumer_messager.py | 11 +++----- .../capif_events/core/notifications.py | 26 +++++-------------- .../api_invocation_logs/core/redis_event.py | 2 +- .../published_apis/core/redis_event.py | 2 +- .../capif_security/core/redis_event.py | 2 +- 7 files changed, 15 insertions(+), 32 deletions(-) diff --git a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_event.py b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_event.py index da494eb..aadbdbb 100644 --- a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_event.py +++ b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_event.py @@ -35,7 +35,7 @@ class RedisEvent(): return json.dumps(self.redis_event, cls=JSONEncoder) def send_event(self): - publisher_ops.publish_message("events-log", self.to_string()) + publisher_ops.publish_message("events", self.to_string()) def __call__(self): return self.redis_event diff --git a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/redis_event.py b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/redis_event.py index da494eb..aadbdbb 100644 --- a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/redis_event.py +++ b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/redis_event.py @@ -35,7 +35,7 @@ class RedisEvent(): return json.dumps(self.redis_event, cls=JSONEncoder) def send_event(self): - publisher_ops.publish_message("events-log", self.to_string()) + publisher_ops.publish_message("events", self.to_string()) def __call__(self): return self.redis_event diff --git a/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py b/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py index 5012448..ce6479a 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py @@ -16,19 +16,16 @@ class Subscriber(): self.notification = Notifications() self.event_ops = InternalEventOperations() self.p = self.r.pubsub() - self.p.subscribe("events", "internal-messages", "events-log") + self.p.subscribe("events", "internal-messages") def listen(self): for raw_message in self.p.listen(): current_app.logger.info(raw_message) if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "events": current_app.logger.info("Event received") - self.notification.send_notifications(raw_message["data"].decode('utf-8')) - elif raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "events-log": - current_app.logger.info("Event-log received") - event_redis=json.loads(raw_message["data"].decode('utf-8')) - current_app.logger.info(json.dumps(event_redis, indent=4)) - self.notification.send_notifications_new(event_redis) + redis_event=json.loads(raw_message["data"].decode('utf-8')) + current_app.logger.info(json.dumps(redis_event, indent=4)) + self.notification.send_notifications(redis_event) elif raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": message, *subscriber_ids = raw_message["data"].decode('utf-8').split(":") if message == "invoker-removed" and len(subscriber_ids)>0: 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 0ece38e..2229a71 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py @@ -16,21 +16,7 @@ class Notifications(): def __init__(self): self.events_ops = InternalEventOperations() - def send_notifications(self, event): - current_app.logger.info("Received event, sending notifications") - subscriptions = self.events_ops.get_event_subscriptions(event) - - try: - for sub in subscriptions: - url = sub["notification_destination"] - data = EventNotification(sub["subscription_id"], events=event) - self.request_post(url, data) - - except Exception as e: - current_app.logger.error("An exception occurred ::" + str(e)) - return False - - def send_notifications_new(self, redis_event): + def send_notifications(self, redis_event): try: if redis_event.get('event', None) == None: raise("Event value is not present on received event from REDIS") @@ -69,9 +55,9 @@ class Notifications(): async def send(self, url, data): try: response = await self.send_request(url, data) - print(response) + current_app.logger.debug(response) except asyncio.TimeoutError: - print("Timeout: Request timeout") - - - + current_app.logger.error("Timeout: Request timeout") + except Exception as e: + current_app.logger.error("An exception occurred sending notification::" + str(e)) + return False diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py index da494eb..aadbdbb 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py @@ -35,7 +35,7 @@ class RedisEvent(): return json.dumps(self.redis_event, cls=JSONEncoder) def send_event(self): - publisher_ops.publish_message("events-log", self.to_string()) + publisher_ops.publish_message("events", self.to_string()) def __call__(self): return self.redis_event diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/redis_event.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/redis_event.py index da494eb..aadbdbb 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/redis_event.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/redis_event.py @@ -35,7 +35,7 @@ class RedisEvent(): return json.dumps(self.redis_event, cls=JSONEncoder) def send_event(self): - publisher_ops.publish_message("events-log", self.to_string()) + publisher_ops.publish_message("events", self.to_string()) def __call__(self): return self.redis_event diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/redis_event.py b/services/TS29222_CAPIF_Security_API/capif_security/core/redis_event.py index da494eb..aadbdbb 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/core/redis_event.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/redis_event.py @@ -35,7 +35,7 @@ class RedisEvent(): return json.dumps(self.redis_event, cls=JSONEncoder) def send_event(self): - publisher_ops.publish_message("events-log", self.to_string()) + publisher_ops.publish_message("events", self.to_string()) def __call__(self): return self.redis_event -- GitLab From 6d760a6053be10e2884affff14114461fa5d5218 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Tue, 28 May 2024 17:09:58 +0200 Subject: [PATCH 22/26] Some improvements over robot framework docker image --- services/run_capif_tests.sh | 3 ++- tests/resources/common.resource | 32 ++++++------------------------- tools/robot/Dockerfile | 12 ------------ tools/robot/basicRequirements.txt | 1 - tools/robot/basicRobotInstall.sh | 10 +--------- 5 files changed, 9 insertions(+), 49 deletions(-) diff --git a/services/run_capif_tests.sh b/services/run_capif_tests.sh index 06de230..8f9f414 100755 --- a/services/run_capif_tests.sh +++ b/services/run_capif_tests.sh @@ -25,7 +25,8 @@ CAPIF_VAULT_PORT=8200 # CAPIF_VAULT_TOKEN=dev-only-token CAPIF_VAULT_TOKEN=read-ca-token -MOCK_SERVER_URL=http://192.168.0.119:9090 + +MOCK_SERVER_URL=http://192.168.0.11:9090 echo "CAPIF_HOSTNAME = $CAPIF_HOSTNAME" diff --git a/tests/resources/common.resource b/tests/resources/common.resource index d02891d..1be4429 100644 --- a/tests/resources/common.resource +++ b/tests/resources/common.resource @@ -32,7 +32,7 @@ ${CAPIF_CALLBACK_PORT} 8086 ${REGISTER_ADMIN_USER} admin ${REGISTER_ADMIN_PASSWORD} password123 -${MOCK_SERVER_URL} http://192.168.0.14:9090 +${MOCK_SERVER_URL} ${DISCOVER_URL} /service-apis/v1/allServiceAPIs?api-invoker-id= @@ -91,33 +91,13 @@ Test ${TEST NAME} Currently Not Supported Log Test "${TEST NAME}" Currently not supported WARN Skip Test "${TEST NAME}" Currently not supported -Start Mock server - Log Starting mock Server for Robot Framework. - - # Run Process pip3 install -r /opt/robot-tests/tests/libraries/mock_server/requirements.txt - ${server_process}= Start Process python3 /opt/robot-tests/tests/libraries/mock_server/mock_server.py shell=True - # Log PID: ${server_process.pid} - Log output: ${server_process.stdout} - - Sleep 5m - Create Session mockserver ${MOCK_SERVER_URL} - - ${endpoint}= Set Variable /testing - - ${json}= Create Dictionary events="SERVICE_API_INVOCATION_SUCCESS" subscriptionId="255545008cd3937f5554a3651bbfb9" - - ${resp}= POST On Session - ... mockserver - ... ${endpoint} - ... json=${json} - ... expected_status=any - ... verify=False - - ${result}= Terminate Process ${server_process} - Check Mock Server Log Checking mock Server for Robot Framework. + IF "${MOCK_SERVER_URL}" == "" + Fail Mock Server Url is not setup on Tests execution, check MOCK_SERVER_URL variable mockserver-not-ready + END + Create Session mockserver ${MOCK_SERVER_URL} ${endpoint}= Set Variable /requests_list @@ -128,7 +108,7 @@ Check Mock Server ... expected_status=any ... verify=False - Status Should Be 200 ${resp} + Status Should Be 200 ${resp} Mock Server response is not 200 OK at ${MOCK_SERVER_URL}, check server status. Clean Mock Server Log Checking mock Server for Robot Framework. diff --git a/tools/robot/Dockerfile b/tools/robot/Dockerfile index 261b1e4..5cdc3cf 100644 --- a/tools/robot/Dockerfile +++ b/tools/robot/Dockerfile @@ -64,18 +64,6 @@ RUN python3.10 -m venv /opt/venv ENV PLAYWRIGHT_BROWSERS_PATH=$HOME/pw-browsers -# Instalación de nvm y node a la última versión -# RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash -# # RUN source /root/.bashrc -# # RUN export NVM_DIR="$HOME/.nvm" \ -# # [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm \ -# # [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion - -# RUN nvm install node -# # RUN npx playwright install -# RUN yes|npx playwright install-deps - - ADD basicRequirements.txt /root/ ADD basicRobotInstall.sh /root/ diff --git a/tools/robot/basicRequirements.txt b/tools/robot/basicRequirements.txt index 282a034..aa3e60e 100644 --- a/tools/robot/basicRequirements.txt +++ b/tools/robot/basicRequirements.txt @@ -71,7 +71,6 @@ requests==2.28.1 rfc3987==1.3.8 robotframework==7.0 robotframework-archivelibrary == 0.4.2 -robotframework-browser==18.3.0 robotframework-httpctrl==0.3.1 robotframework-lint==1.1 robotframework-mongodb-library==3.2 diff --git a/tools/robot/basicRobotInstall.sh b/tools/robot/basicRobotInstall.sh index ba66010..ae0bd6b 100644 --- a/tools/robot/basicRobotInstall.sh +++ b/tools/robot/basicRobotInstall.sh @@ -1,14 +1,6 @@ #!/bin/bash echo "Installing basic software related with robotFramework" -source /opt/venv/bin/activate; +source /opt/venv/bin/activate pip install --upgrade pip -curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash -source /root/.bashrc -nvm install node -yes|npx playwright install-deps pip install -r $1 -rfbrowser clean-node -rfbrowser init --skip-browsers -npx playwright install -npx playwright install-deps echo "Robot framework installed" -- GitLab From 32ece8bc71265e0a47bcc18cac9c8abb08aab941 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Wed, 29 May 2024 09:33:04 +0200 Subject: [PATCH 23/26] remove not used env variable at robot docker image --- tools/robot/Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/robot/Dockerfile b/tools/robot/Dockerfile index 5cdc3cf..347f226 100644 --- a/tools/robot/Dockerfile +++ b/tools/robot/Dockerfile @@ -62,8 +62,6 @@ RUN apt-get install -y --fix-missing python3.10 python3.10-venv python3.10-dev RUN mkdir /opt/venv RUN python3.10 -m venv /opt/venv -ENV PLAYWRIGHT_BROWSERS_PATH=$HOME/pw-browsers - ADD basicRequirements.txt /root/ ADD basicRobotInstall.sh /root/ -- GitLab From 458c1c712c6d83779f1652983209ba7621180e1a Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Wed, 29 May 2024 11:35:41 +0200 Subject: [PATCH 24/26] Code Refactor on Event tests --- .../CAPIF Api Events/capif_events_api.robot | 412 +++++++++--------- tests/resources/common.resource | 25 +- tests/resources/common/basicRequests.robot | 36 -- 3 files changed, 234 insertions(+), 239 deletions(-) diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index 950cdad..d5e02d2 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -142,9 +142,8 @@ Deletes an individual CAPIF Event Subscription with invalid SubscriptionId Invoker receives Service API Invocation events [Tags] capif_api_events-6 mockserver - # Start Mock server - Check Mock Server - Clean Mock Server + # Initialize Mock server + Init Mock Server # Register APF ${register_user_info}= Provider Default Registration @@ -203,33 +202,19 @@ Invoker receives Service API Invocation events Check Response Variable Type And Values ${resp} 201 InvocationLog ${resource_url}= Check Location Header ${resp} ${LOCATION_LOGGING_RESOURCE_REGEX} - Sleep 3s - - # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. - ${resp}= Get Mock Server Messages - ${notification_events_on_mock_server}= Set Variable ${resp.json()} - # Check if message follow EventNotification definition. - Check Variable ${notification_events_on_mock_server} EventNotification - - # Create check Events to ensure all notifications were received - ${check_events} ${check_events_length}= Create Events From InvocationLogs + # Check Event Notifications + ## Create check Events to ensure all notifications were received + ${events_expected}= Create Events From InvocationLogs ... ${subscription_id} ... ${request_body} - - # Number of events received must be equal than events expected - Length Should Be ${notification_events_on_mock_server} ${check_events_length} - # Check if events received are the same than expected - FOR ${event} IN @{check_events} - Log ${event} - List Should Contain Value ${notification_events_on_mock_server} ${event} - END + ## Check Events Expected towards received notifications at mock server + Check Mock Server Notification Events ${events_expected} Invoker subscribe to Service API Available and Unavailable events [Tags] capif_api_events-7 mockserver - # Start Mock server - Check Mock Server - Clean Mock Server + # Initialize Mock server + Init Mock Server # Register APF ${register_user_info_provider}= Provider Default Registration @@ -284,41 +269,22 @@ Invoker subscribe to Service API Available and Unavailable events Status Should Be 204 ${resp} - # Check Results - - Sleep 3s - # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. - ${resp}= Get Mock Server Messages - ${notification_events_on_mock_server}= Set Variable ${resp.json()} - # Check if message follow EventNotification definition. - Check Variable ${notification_events_on_mock_server} EventNotification - - # Create Notification Events expected to be received - ${api_id_1}= Fetch From Right ${resource_url_1.path} / - ${notification_event_expected_removed}= Create Notification Event - ... ${subscription_id} - ... SERVICE_API_UNAVAILABLE - ... apiIds=${api_id_1} - Check Variable ${notification_event_expected_removed} EventNotification - ${api_id_2}= Fetch From Right ${resource_url_2.path} / - ${notification_event_expected_created}= Create Notification Event - ... ${subscription_id} - ... SERVICE_API_AVAILABLE - ... apiIds=${api_id_2} - Check Variable ${notification_event_expected_created} EventNotification - - # Check results - Length Should Be ${notification_events_on_mock_server} 2 - List Should Contain Value ${notification_events_on_mock_server} ${notification_event_expected_removed} - List Should Contain Value ${notification_events_on_mock_server} ${notification_event_expected_created} - + # Check Event Notifications + ## Create check Events to ensure all notifications were received + ${service_api_available_resources}= Create List ${resource_url_2} + ${service_api_unavailable_resources}= Create List ${resource_url_1} + ${events_expected}= Create Expected Events For Service API Notifications + ... subscription_id=${subscription_id} + ... service_api_available_resources=${service_api_available_resources} + ... service_api_unavailable_resources=${service_api_unavailable_resources} + ## Check Events Expected towards received notifications at mock server + Check Mock Server Notification Events ${events_expected} Invoker subscribe to Service API Update [Tags] capif_api_events-8 mockserver - # Start Mock server - Check Mock Server - Clean Mock Server + # Initialize Mock server + Init Mock Server # Register APF ${register_user_info_provider}= Provider Default Registration @@ -360,10 +326,10 @@ Invoker subscribe to Service API Update ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} # Update Service API - ${request_body_modified}= Create Service Api Description service_1_modified + ${service_api_description_modified}= Create Service Api Description service_1_modified ${resp}= Put Request Capif ... ${resource_url.path} - ... json=${request_body_modified} + ... json=${service_api_description_modified} ... server=${CAPIF_HTTPS_URL} ... verify=ca.crt ... username=${APF_PROVIDER_USERNAME} @@ -371,39 +337,23 @@ Invoker subscribe to Service API Update Check Response Variable Type And Values ${resp} 200 ServiceAPIDescription ... apiName=service_1_modified - # Check Results - Sleep 3s - # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. - ${resp}= Get Mock Server Messages - ${notification_events_on_mock_server}= Set Variable ${resp.json()} - # Check if message follow EventNotification definition. - Check Variable ${notification_events_on_mock_server} EventNotification - - # Create Notification Events expected to be received - ${api_id}= Fetch From Right ${resource_url.path} / - Set To Dictionary ${request_body_modified} apiId=${api_id} - ${notification_event_expected}= Create Notification Event - ... ${subscription_id} - ... SERVICE_API_UPDATE - ... serviceAPIDescriptions=${request_body_modified} - Check Variable ${notification_event_expected} EventNotification - - # Check results - Length Should Be ${notification_events_on_mock_server} 1 - List Should Contain Value ${notification_events_on_mock_server} ${notification_event_expected} + # Check Event Notifications + ## Create check Events to ensure all notifications were received + ${events_expected}= Create Expected Service Update Event ${subscription_id} ${resource_url} ${service_api_description_modified} + ## Check Events Expected towards received notifications at mock server + Check Mock Server Notification Events ${events_expected} Provider subscribe to API Invoker events [Tags] capif_api_events-9 mockserver - # Start Mock server - Check Mock Server - Clean Mock Server + # Initialize Mock server + Init Mock Server # Register APF ${register_user_info_provider}= Provider Default Registration # Subscribe to events - ${events_list}= Create List API_INVOKER_ONBOARDED API_INVOKER_UPDATED API_INVOKER_OFFBOARDED + ${events_list}= Create List API_INVOKER_ONBOARDED API_INVOKER_UPDATED API_INVOKER_OFFBOARDED ${request_body}= Create Events Subscription ... events=@{events_list} ... notificationDestination=${MOCK_SERVER_URL}/testing @@ -450,54 +400,19 @@ Provider subscribe to API Invoker events # Check Remove Should Be Equal As Strings ${resp.status_code} 204 - # Check Results - Sleep 3s - # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. - ${resp}= Get Mock Server Messages - ${notification_events_on_mock_server}= Set Variable ${resp.json()} - # Check if message follow EventNotification definition. - Check Variable ${notification_events_on_mock_server} EventNotification - - ## Create events expected - ${events_expected}= Create List - ${api_invoker_id}= Set Variable ${register_user_info_invoker['api_invoker_id']} - # Create Notification Events expected to be received for Onboard event - ${event_expected}= Create Notification Event - ... ${subscription_id} - ... API_INVOKER_ONBOARDED - ... apiInvokerIds=${api_invoker_id} - Append To List ${events_expected} ${event_expected} - - # Create Notification Events expected to be received for Updated event - ${event_expected}= Create Notification Event - ... ${subscription_id} - ... API_INVOKER_UPDATED - ... apiInvokerIds=${api_invoker_id} - Append To List ${events_expected} ${event_expected} - - # Create Notification Events expected to be received for Offboard event - ${event_expected}= Create Notification Event + # Check Event Notifications + ## Create check Events to ensure all notifications were received + ${events_expected}= Create Expected Api Invoker Events ... ${subscription_id} - ... API_INVOKER_OFFBOARDED - ... apiInvokerIds=${api_invoker_id} - Append To List ${events_expected} ${event_expected} - - Check Variable ${events_expected} EventNotification - - # Check results - ${events_expected_length}= Get Length ${events_expected} - Length Should Be ${notification_events_on_mock_server} ${events_expected_length} - FOR ${event_expected} IN @{events_expected} - Log ${event_expected} - List Should Contain Value ${notification_events_on_mock_server} ${event_expected} - END + ... ${register_user_info_invoker['api_invoker_id']} + ## Check Events Expected towards received notifications at mock server + Check Mock Server Notification Events ${events_expected} Invoker subscribed to ACL update event [Tags] capif_api_events-10 mockserver - # Start Mock server - Check Mock Server - Clean Mock Server + # Initialize Mock server + Init Mock Server # Register APF ${register_user_info_provider}= Provider Default Registration @@ -507,7 +422,7 @@ Invoker subscribed to ACL update event ... ${register_user_info_provider} # Store apiId1 - ${serviceApiId}= Set Variable ${service_api_description_published['apiId']} + ${service_api_id}= Set Variable ${service_api_description_published['apiId']} # Register INVOKER ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding @@ -528,7 +443,6 @@ Invoker subscribed to ACL update event Check Response Variable Type And Values ${resp} 201 EventSubscription ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} - # Test ${discover_response}= Get Request Capif ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']}&aef-id=${register_user_info_provider['aef_id']} @@ -554,7 +468,7 @@ Invoker subscribed to ACL update event ${resource_url}= Check Location Header ${resp} ${LOCATION_SECURITY_RESOURCE_REGEX} ${resp}= Get Request Capif - ... /access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${register_user_info_provider['aef_id']} + ... /access-control-policy/v1/accessControlPolicyList/${service_api_id}?aef-id=${register_user_info_provider['aef_id']} ... server=${CAPIF_HTTPS_URL} ... verify=ca.crt ... username=${AEF_PROVIDER_USERNAME} @@ -567,42 +481,22 @@ Invoker subscribed to ACL update event ... ${resp.json()['apiInvokerPolicies'][0]['apiInvokerId']} ... ${register_user_info_invoker['api_invoker_id']} - ${api_invoker_policies}= Set Variable ${resp.json()['apiInvokerPolicies']} - - # Check Results - Sleep 3s - # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. - ${resp}= Get Mock Server Messages - ${notification_events_on_mock_server}= Set Variable ${resp.json()} - - ## Create events expected - ${acc_ctrl_pol_list}= Create Dictionary apiId=${serviceApiId} apiInvokerPolicies=${api_invoker_policies} - Check Variable ${acc_ctrl_pol_list} AccessControlPolicyListExt + ${api_invoker_policies}= Set Variable ${resp.json()['apiInvokerPolicies']} - ${events_expected}= Create List - ${event_expected}= Create Notification Event + # Check Event Notifications + ## Create check Events to ensure all notifications were received + ${events_expected}= Create Expected Access Control Policy Update Event ... ${subscription_id} - ... ACCESS_CONTROL_POLICY_UPDATE - ... accCtrlPolList=${acc_ctrl_pol_list} - - Append To List ${events_expected} ${event_expected} - - Check Variable ${events_expected} EventNotification - - # Check results - ${events_expected_length}= Get Length ${events_expected} - Length Should Be ${notification_events_on_mock_server} ${events_expected_length} - FOR ${event_expected} IN @{events_expected} - Log ${event_expected} - List Should Contain Value ${notification_events_on_mock_server} ${event_expected} - END + ... ${service_api_id} + ... ${api_invoker_policies} + ## Check Events Expected towards received notifications at mock server + Check Mock Server Notification Events ${events_expected} Provider receives an ACL unavailable event when invoker remove Security Context. [Tags] capif_api_events-11 mockserver - # Start Mock server - Check Mock Server - Clean Mock Server + # Initialize Mock server + Init Mock Server # Register APF ${register_user_info_provider}= Provider Default Registration @@ -633,7 +527,6 @@ Provider receives an ACL unavailable event when invoker remove Security Context. Check Response Variable Type And Values ${resp} 201 EventSubscription ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} - # Test ${discover_response}= Get Request Capif ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']}&aef-id=${register_user_info_provider['aef_id']} @@ -667,34 +560,17 @@ Provider receives an ACL unavailable event when invoker remove Security Context. Status Should Be 204 ${resp} - # Check Results - Sleep 3s - # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. - ${resp}= Get Mock Server Messages - ${notification_events_on_mock_server}= Set Variable ${resp.json()} - - ## Create events expected - ${events_expected}= Create List - ${event_expected}= Create Notification Event - ... ${subscription_id} - ... ACCESS_CONTROL_POLICY_UNAVAILABLE - Append To List ${events_expected} ${event_expected} - Check Variable ${events_expected} EventNotification - - # Check results - ${events_expected_length}= Get Length ${events_expected} - Length Should Be ${notification_events_on_mock_server} ${events_expected_length} - FOR ${event_expected} IN @{events_expected} - Log ${event_expected} - List Should Contain Value ${notification_events_on_mock_server} ${event_expected} - END + # Check Event Notifications + ## Create check Events to ensure all notifications were received + ${events_expected}= Create Expected Access Control Policy Unavailable ${subscription_id} + ## Check Events Expected towards received notifications at mock server + Check Mock Server Notification Events ${events_expected} Invoker receives an Invoker Authorization Revoked and ACL unavailable event when Provider revoke Invoker Authorization. [Tags] capif_api_events-12 mockserver - # Start Mock server - Check Mock Server - Clean Mock Server + # Initialize Mock server + Init Mock Server # Register APF ${register_user_info_provider}= Provider Default Registration @@ -710,7 +586,7 @@ Invoker receives an Invoker Authorization Revoked and ACL unavailable event when ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding # Subscribe to events - ${events_list}= Create List ACCESS_CONTROL_POLICY_UNAVAILABLE API_INVOKER_AUTHORIZATION_REVOKED + ${events_list}= Create List ACCESS_CONTROL_POLICY_UNAVAILABLE API_INVOKER_AUTHORIZATION_REVOKED ${request_body}= Create Events Subscription ... events=@{events_list} ... notificationDestination=${MOCK_SERVER_URL}/testing @@ -765,30 +641,162 @@ Invoker receives an Invoker Authorization Revoked and ACL unavailable event when # Check Results Status Should Be 204 ${resp} - # Check Results - Sleep 3s - # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. - ${resp}= Get Mock Server Messages - ${notification_events_on_mock_server}= Set Variable ${resp.json()} + # Check Event Notifications + ## Create check Events to ensure all notifications were received + ${events_expected}= Create Expected Access Control Policy Unavailable ${subscription_id} + ${events_expected}= Create Expected Api Invoker Authorization Revoked + ... ${subscription_id} + ... events_expected=${events_expected} + ## Check Events Expected towards received notifications at mock server + Check Mock Server Notification Events ${events_expected} + + +*** Keywords *** +Create Events From InvocationLogs + [Arguments] ${subscription_id} ${invocation_log} ${events_expected}=${NONE} + IF ${events_expected} == ${NONE} + ${events_expected}= Create List + END + + # Now we create the expected events received at notification server according to logs sent to loggin service in order to check if all are present. + ${invocation_log_base}= Copy Dictionary ${invocation_log} deepcopy=True + # Store log array because each log will be notified in one Event Notification + ${invocation_log_logs}= Copy List ${invocation_log_base['logs']} + # Remove logs array from invocationLog data + Remove From Dictionary ${invocation_log_base} logs + + FOR ${log} IN @{invocation_log_logs} + Log Dictionary ${log} + ${invocation_logs}= Copy Dictionary ${invocation_log_base} deepcopy=True + + # Get Event Enum for this result + ${event_enum}= Set Variable + IF ${log['result']} >= 200 and ${log['result']} < 300 + ${event_enum}= Set Variable SERVICE_API_INVOCATION_SUCCESS + ELSE + ${event_enum}= Set Variable SERVICE_API_INVOCATION_FAILURE + END + # Create a log array with only one component + ${log_list}= Create List ${log} + # Setup logs array with previously created list + Set To Dictionary ${invocation_logs} logs=${log_list} + ${event_expected}= Create Notification Event ${subscription_id} ${event_enum} invocationLogs=${invocation_logs} + Append To List ${events_expected} ${event_expected} + END + + RETURN ${events_expected} +Create Expected Events For Service API Notifications + [Arguments] + ... ${subscription_id} + ... ${service_api_available_resources}=${NONE} + ... ${service_api_unavailable_resources}=${NONE} + ... ${events_expected}=${NONE} + + IF ${events_expected} == ${NONE} + ${events_expected}= Create List + END + + FOR ${service_api_available_resource} IN @{service_api_available_resources} + Log ${service_api_available_resource} + ${api_id}= Fetch From Right ${service_api_available_resource.path} / + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... SERVICE_API_AVAILABLE + ... apiIds=${api_id} + Append To List ${events_expected} ${event_expected} + END + + FOR ${service_api_unavailable_resource} IN @{service_api_unavailable_resources} + Log ${service_api_unavailable_resource} + ${api_id}= Fetch From Right ${service_api_unavailable_resource.path} / + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... SERVICE_API_UNAVAILABLE + ... apiIds=${api_id} + Append To List ${events_expected} ${event_expected} + END + + RETURN ${events_expected} +Create Expected Api Invoker Events + [Arguments] ${subscription_id} ${api_invoker_id} ${events_expected}=${NONE} + IF ${events_expected} == ${NONE} + ${events_expected}= Create List + END ## Create events expected - ${events_expected}= Create List - ## ACCESS_CONTROL_POLICY_UNAVAILABLE event + # Create Notification Events expected to be received for Onboard event + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... API_INVOKER_ONBOARDED + ... apiInvokerIds=${api_invoker_id} + Append To List ${events_expected} ${event_expected} + + # Create Notification Events expected to be received for Updated event + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... API_INVOKER_UPDATED + ... apiInvokerIds=${api_invoker_id} + Append To List ${events_expected} ${event_expected} + + # Create Notification Events expected to be received for Offboard event + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... API_INVOKER_OFFBOARDED + ... apiInvokerIds=${api_invoker_id} + Append To List ${events_expected} ${event_expected} + + RETURN ${events_expected} + +Create Expected Access Control Policy Update Event + [Arguments] ${subscription_id} ${service_api_id} ${api_invoker_policies} ${events_expected}=${NONE} + IF ${events_expected} == ${NONE} + ${events_expected}= Create List + END + ${acc_ctrl_pol_list}= Create Dictionary apiId=${service_api_id} apiInvokerPolicies=${api_invoker_policies} + Check Variable ${acc_ctrl_pol_list} AccessControlPolicyListExt + + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... ACCESS_CONTROL_POLICY_UPDATE + ... accCtrlPolList=${acc_ctrl_pol_list} + Append To List ${events_expected} ${event_expected} + + RETURN ${events_expected} + +Create Expected Access Control Policy Unavailable + [Arguments] ${subscription_id} ${events_expected}=${NONE} + IF ${events_expected} == ${NONE} + ${events_expected}= Create List + END ${event_expected}= Create Notification Event ... ${subscription_id} ... ACCESS_CONTROL_POLICY_UNAVAILABLE - Append To List ${events_expected} ${event_expected} - ## API_INVOKER_AUTHORIZATION_REVOKED event + Append To List ${events_expected} ${event_expected} + + RETURN ${events_expected} + +Create Expected Api Invoker Authorization Revoked + [Arguments] ${subscription_id} ${events_expected}=${NONE} + IF ${events_expected} == ${NONE} + ${events_expected}= Create List + END ${event_expected}= Create Notification Event ... ${subscription_id} ... API_INVOKER_AUTHORIZATION_REVOKED - Append To List ${events_expected} ${event_expected} - Check Variable ${events_expected} EventNotification - - # Check results - ${events_expected_length}= Get Length ${events_expected} - Length Should Be ${notification_events_on_mock_server} ${events_expected_length} - FOR ${event_expected} IN @{events_expected} - Log ${event_expected} - List Should Contain Value ${notification_events_on_mock_server} ${event_expected} - END \ No newline at end of file + Append To List ${events_expected} ${event_expected} + RETURN ${events_expected} + +Create Expected Service Update Event + [Arguments] ${subscription_id} ${service_api_resource} ${service_api_descriptions} ${events_expected}=${NONE} + IF ${events_expected} == ${NONE} + ${events_expected}= Create List + END + ${api_id}= Fetch From Right ${service_api_resource.path} / + Set To Dictionary ${service_api_descriptions} apiId=${api_id} + ${events_expected}= Create List + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... SERVICE_API_UPDATE + ... serviceAPIDescriptions=${service_api_descriptions} + Append To List ${events_expected} ${event_expected} + RETURN ${events_expected} \ No newline at end of file diff --git a/tests/resources/common.resource b/tests/resources/common.resource index 1be4429..d4733b8 100644 --- a/tests/resources/common.resource +++ b/tests/resources/common.resource @@ -91,6 +91,10 @@ Test ${TEST NAME} Currently Not Supported Log Test "${TEST NAME}" Currently not supported WARN Skip Test "${TEST NAME}" Currently not supported +Init Mock Server + Check Mock Server + Clean Mock Server + Check Mock Server Log Checking mock Server for Robot Framework. @@ -141,4 +145,23 @@ Get Mock Server Messages Status Should Be 200 ${resp} Log List ${resp.json()} - RETURN ${resp} \ No newline at end of file + RETURN ${resp} + +Check Mock Server Notification Events + [Arguments] ${events_expected} + + Check Variable ${events_expected} EventNotification + # Check results + ${events_expected_length}= Get Length ${events_expected} + + Sleep 3s + # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. + ${resp}= Get Mock Server Messages + ${notification_events_on_mock_server}= Set Variable ${resp.json()} + Check Variable ${notification_events_on_mock_server} EventNotification + + Length Should Be ${notification_events_on_mock_server} ${events_expected_length} + FOR ${event_expected} IN @{events_expected} + Log ${event_expected} + List Should Contain Value ${notification_events_on_mock_server} ${event_expected} + END \ No newline at end of file diff --git a/tests/resources/common/basicRequests.robot b/tests/resources/common/basicRequests.robot index 9ba5f21..335743f 100644 --- a/tests/resources/common/basicRequests.robot +++ b/tests/resources/common/basicRequests.robot @@ -746,39 +746,3 @@ Create Security Context Between invoker and provider Check Response Variable Type And Values ${resp} 201 ServiceSecurity - -Create Events From InvocationLogs - [Arguments] ${subscription_id} ${invocation_log} - - ${events}= Create List - - # Now we create the expected events received at notification server according to logs sent to loggin service in order to check if all are present. - ${invocation_log_base}= Copy Dictionary ${invocation_log} deepcopy=True - # Store log array because each log will be notified in one Event Notification - ${invocation_log_logs}= Copy List ${invocation_log_base['logs']} - # Remove logs array from invocationLog data - Remove From Dictionary ${invocation_log_base} logs - - FOR ${log} IN @{invocation_log_logs} - Log Dictionary ${log} - ${invocation_logs}= Copy Dictionary ${invocation_log_base} deepcopy=True - - # Get Event Enum for this result - ${event_enum}= Set Variable - IF ${log['result']} >= 200 and ${log['result']} < 300 - ${event_enum}= Set Variable SERVICE_API_INVOCATION_SUCCESS - ELSE - ${event_enum}= Set Variable SERVICE_API_INVOCATION_FAILURE - END - # Create a log array with only one component - ${log_list}= Create List ${log} - # Setup logs array with previously created list - Set To Dictionary ${invocation_logs} logs=${log_list} - ${event}= Create Notification Event ${subscription_id} ${event_enum} invocationLogs=${invocation_logs} - Check Variable ${event} EventNotification - Append To List ${events} ${event} - END - - Log List ${events} - ${events_length}= Get Length ${events} - RETURN ${events} ${events_length} -- GitLab From 8235a91295f70618f36f7be65faedde4ee8b9ed7 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Thu, 30 May 2024 12:29:02 +0200 Subject: [PATCH 25/26] minor fixes --- services/check_services_are_running.sh | 33 +++++++++++++++++-- services/docker-compose-capif.yml | 2 -- services/docker-compose-register.yml | 2 -- services/docker-compose-vault.yml | 1 - .../run_mock_server.sh | 8 +++-- 5 files changed, 36 insertions(+), 10 deletions(-) rename {tests/libraries/mock_server => services}/run_mock_server.sh (76%) diff --git a/services/check_services_are_running.sh b/services/check_services_are_running.sh index 3bad399..16de704 100755 --- a/services/check_services_are_running.sh +++ b/services/check_services_are_running.sh @@ -1,12 +1,39 @@ #!/bin/bash +export CAPIF_PRIV_KEY= +export CAPIF_PRIV_KEY_BASE_64= +export MONITORING= + +running="$(docker compose -f docker-compose-vault.yml ps --services --all --filter "status=running")" +services="$(docker compose -f docker-compose-vault.yml ps --services --all)" +if [ "$running" != "$services" ]; then + echo "Following Vault services are not running:" + # Bash specific + comm -13 <(sort <<<"$running") <(sort <<<"$services") + exit 1 +else + echo "All Vault services are running" +fi + running="$(docker compose -f docker-compose-capif.yml ps --services --all --filter "status=running")" services="$(docker compose -f docker-compose-capif.yml ps --services --all)" if [ "$running" != "$services" ]; then - echo "Following services are not running:" + echo "Following CCF services are not running:" + # Bash specific + comm -13 <(sort <<<"$running") <(sort <<<"$services") + exit 1 +else + echo "All CCF services are running" +fi + +running="$(docker compose -f docker-compose-register.yml ps --services --all --filter "status=running")" +services="$(docker compose -f docker-compose-register.yml ps --services --all)" +if [ "$running" != "$services" ]; then + echo "Following Register services are not running:" # Bash specific comm -13 <(sort <<<"$running") <(sort <<<"$services") exit 1 else - echo "All services are running" - exit 0 + echo "All Register services are running" fi + +exit 0 diff --git a/services/docker-compose-capif.yml b/services/docker-compose-capif.yml index d05518f..779df21 100644 --- a/services/docker-compose-capif.yml +++ b/services/docker-compose-capif.yml @@ -1,5 +1,3 @@ -version: '3.7' - services: redis: image: "redis:alpine" diff --git a/services/docker-compose-register.yml b/services/docker-compose-register.yml index 5363e01..b0e5571 100644 --- a/services/docker-compose-register.yml +++ b/services/docker-compose-register.yml @@ -1,5 +1,3 @@ -version: '3.7' - services: register: build: diff --git a/services/docker-compose-vault.yml b/services/docker-compose-vault.yml index e0bb7bc..82a38d0 100644 --- a/services/docker-compose-vault.yml +++ b/services/docker-compose-vault.yml @@ -1,4 +1,3 @@ -version: '3.7' services: vault: build: diff --git a/tests/libraries/mock_server/run_mock_server.sh b/services/run_mock_server.sh similarity index 76% rename from tests/libraries/mock_server/run_mock_server.sh rename to services/run_mock_server.sh index 10c4ba2..b6f1035 100755 --- a/tests/libraries/mock_server/run_mock_server.sh +++ b/services/run_mock_server.sh @@ -8,6 +8,10 @@ help() { exit 1 } +cd .. +REPOSITORY_BASE_FOLDER=${PWD} +MOCK_SERVER_FOLDER=${PWD}/tests/libraries/mock_server + IP=0.0.0.0 PORT=9090 @@ -35,6 +39,6 @@ while getopts ":i:p:h" opt; do done echo Robot Framework Mock Server will listen on $IP:$PORT -pip install -r requirements.txt +pip install -r ${MOCK_SERVER_FOLDER}/requirements.txt -IP=$IP PORT=$PORT python mock_server.py +IP=$IP PORT=$PORT python ${MOCK_SERVER_FOLDER}/mock_server.py -- GitLab From cb619ca67217d252e5b3f805ead89ea309151990 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Tue, 4 Jun 2024 17:18:01 +0200 Subject: [PATCH 26/26] Update mock server, to be included in a simply way to test locally --- services/clean_capif_docker_services.sh | 9 +++++-- services/clean_mock_server.sh | 16 +++++++++++ services/docker-compose-mock-server.yml | 20 ++++++++++++++ services/mock_server/Dockerfile | 21 +++++++++++++++ .../mock_server/mock_server.py | 0 .../mock_server/requirements.txt | 0 services/run.sh | 27 +++++++++++++++++-- services/run_capif_tests.sh | 4 +-- services/run_mock_server.sh | 15 ++++++----- services/show_logs.sh | 9 +++++-- 10 files changed, 107 insertions(+), 14 deletions(-) create mode 100755 services/clean_mock_server.sh create mode 100644 services/docker-compose-mock-server.yml create mode 100644 services/mock_server/Dockerfile rename {tests/libraries => services}/mock_server/mock_server.py (100%) rename {tests/libraries => services}/mock_server/requirements.txt (100%) diff --git a/services/clean_capif_docker_services.sh b/services/clean_capif_docker_services.sh index 1bc6d6d..cfc5b71 100755 --- a/services/clean_capif_docker_services.sh +++ b/services/clean_capif_docker_services.sh @@ -6,6 +6,7 @@ help() { echo " -v : Clean vault service" echo " -r : Clean register service" echo " -m : Clean monitoring service" + echo " -s : Clean Robot Mock service" echo " -a : Clean all services" echo " -h : show this help" exit 1 @@ -21,7 +22,7 @@ FILES=() echo "${FILES[@]}" # Read params -while getopts "cvrahm" opt; do +while getopts "cvrahms" opt; do case $opt in c) echo "Remove Capif services" @@ -39,9 +40,13 @@ while getopts "cvrahm" opt; do echo "Remove monitoring service" FILES+=("../monitoring/docker-compose.yml") ;; + s) + echo "Robot Mock Server" + FILES+=("docker-compose-mock-server.yml") + ;; a) echo "Remove all services" - FILES=("docker-compose-capif.yml" "docker-compose-vault.yml" "docker-compose-register.yml" "../monitoring/docker-compose.yml") + FILES=("docker-compose-capif.yml" "docker-compose-vault.yml" "docker-compose-register.yml" "docker-compose-mock-server.yml" "../monitoring/docker-compose.yml") ;; h) help diff --git a/services/clean_mock_server.sh b/services/clean_mock_server.sh new file mode 100755 index 0000000..5ea886c --- /dev/null +++ b/services/clean_mock_server.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +FILE="docker-compose-mock-server.yml" + +echo "Executing 'docker compose down' for file $FILE" +docker compose -f "$FILE" down --rmi all +status=$? + if [ $status -eq 0 ]; then + echo "*** Removed Service from $FILE ***" + else + echo "*** Some services of $FILE failed on clean ***" + fi + +docker volume prune --all --force + +echo "Clean complete." diff --git a/services/docker-compose-mock-server.yml b/services/docker-compose-mock-server.yml new file mode 100644 index 0000000..1415b43 --- /dev/null +++ b/services/docker-compose-mock-server.yml @@ -0,0 +1,20 @@ +services: + mock-server: + build: + context: ./mock_server + ports: + - 9090:9090 + volumes: + - ./mock_server:/usr/src/app + extra_hosts: + - host.docker.internal:host-gateway + restart: unless-stopped + image: public.ecr.aws/o2v4a8t6/opencapif/mock_server:latest + +networks: + default: + name: capif-network + external: true + + + diff --git a/services/mock_server/Dockerfile b/services/mock_server/Dockerfile new file mode 100644 index 0000000..6d72bf5 --- /dev/null +++ b/services/mock_server/Dockerfile @@ -0,0 +1,21 @@ +# start by pulling the python image +FROM python:3.10.0-alpine + +# copy the requirements file into the image +COPY ./requirements.txt /app/requirements.txt + +# switch working directory +WORKDIR /app + +# install the dependencies and packages in the requirements file +RUN pip install -r requirements.txt + +# copy every content from the local file to the image +COPY . /app + +EXPOSE 9090 + +# configure the container to run in an executed manner +ENTRYPOINT [ "python" ] + +CMD ["mock_server.py" ] \ No newline at end of file diff --git a/tests/libraries/mock_server/mock_server.py b/services/mock_server/mock_server.py similarity index 100% rename from tests/libraries/mock_server/mock_server.py rename to services/mock_server/mock_server.py diff --git a/tests/libraries/mock_server/requirements.txt b/services/mock_server/requirements.txt similarity index 100% rename from tests/libraries/mock_server/requirements.txt rename to services/mock_server/requirements.txt diff --git a/services/run.sh b/services/run.sh index 83011c3..4465aaa 100755 --- a/services/run.sh +++ b/services/run.sh @@ -3,6 +3,7 @@ help() { echo "Usage: $1 " echo " -c : Setup different hostname for capif" + echo " -s : Run Mock server" echo " -m : Clean monitoring service" echo " -h : show this help" exit 1 @@ -12,10 +13,14 @@ HOSTNAME=capifcore MONITORING_STATE=false DEPLOY=all -#Needed to avoid write permissions on bind volumes with prometheus and grafana +# Needed to avoid write permissions on bind volumes with prometheus and grafana DUID=$(id -u) DGID=$(id -g) +# Mock Server configuration +IP=0.0.0.0 +PORT=9090 + # Get docker compose version docker_version=$(docker compose version --short | cut -d',' -f1) IFS='.' read -ra version_components <<< "$docker_version" @@ -28,7 +33,7 @@ else fi # Read params -while getopts ":c:mh" opt; do +while getopts ":c:msh" opt; do case $opt in c) HOSTNAME="$OPTARG" @@ -36,6 +41,9 @@ while getopts ":c:mh" opt; do m) MONITORING_STATE=true ;; + s) + ROBOT_MOCK_SERVER=true + ;; h) help ;; @@ -97,6 +105,21 @@ if [ $status -eq 0 ]; then echo "*** Register Service are running ***" else echo "*** Register Service failed to start ***" + exit $status +fi + +if [ "$ROBOT_MOCK_SERVER" == "true" ] ; then + echo '***Robot Mock Server set as true***' + echo '***Creating Robot Mock Server stack***' + + IP=$IP PORT=$PORT docker compose -f "docker-compose-mock-server.yml" up --detach + status=$? + if [ $status -eq 0 ]; then + echo "*** Monitoring Stack Runing ***" + else + echo "*** Monitoring Stack failed to start ***" + exit $status + fi fi exit $status diff --git a/services/run_capif_tests.sh b/services/run_capif_tests.sh index 8f9f414..7762fcd 100755 --- a/services/run_capif_tests.sh +++ b/services/run_capif_tests.sh @@ -26,7 +26,7 @@ CAPIF_VAULT_PORT=8200 CAPIF_VAULT_TOKEN=read-ca-token -MOCK_SERVER_URL=http://192.168.0.11:9090 +MOCK_SERVER_URL=http://mock-server:9090 echo "CAPIF_HOSTNAME = $CAPIF_HOSTNAME" @@ -66,7 +66,7 @@ docker run -ti --rm --network="host" \ --add-host host.docker.internal:host-gateway \ --add-host vault:host-gateway \ --add-host register:host-gateway \ - --add-host mockserver:host-gateway \ + --add-host mock-server:host-gateway \ -v $TEST_FOLDER:/opt/robot-tests/tests \ -v $RESULT_FOLDER:/opt/robot-tests/results ${DOCKER_ROBOT_IMAGE}:${DOCKER_ROBOT_IMAGE_VERSION} \ --variable CAPIF_HOSTNAME:$CAPIF_HOSTNAME \ diff --git a/services/run_mock_server.sh b/services/run_mock_server.sh index b6f1035..5a194c4 100755 --- a/services/run_mock_server.sh +++ b/services/run_mock_server.sh @@ -8,10 +8,6 @@ help() { exit 1 } -cd .. -REPOSITORY_BASE_FOLDER=${PWD} -MOCK_SERVER_FOLDER=${PWD}/tests/libraries/mock_server - IP=0.0.0.0 PORT=9090 @@ -39,6 +35,13 @@ while getopts ":i:p:h" opt; do done echo Robot Framework Mock Server will listen on $IP:$PORT -pip install -r ${MOCK_SERVER_FOLDER}/requirements.txt -IP=$IP PORT=$PORT python ${MOCK_SERVER_FOLDER}/mock_server.py +IP=$IP PORT=$PORT docker compose -f "docker-compose-mock-server.yml" up --detach --build + +status=$? +if [ $status -eq 0 ]; then + echo "*** All Capif services are running ***" +else + echo "*** Some Capif services failed to start ***" + exit $status +fi diff --git a/services/show_logs.sh b/services/show_logs.sh index b53641d..c2bcf52 100755 --- a/services/show_logs.sh +++ b/services/show_logs.sh @@ -5,6 +5,7 @@ help() { echo " -c : Show capif services" echo " -v : Show vault service" echo " -r : Show register service" + echo " -s : Show Robot Mock Server service" echo " -m : Show monitoring service" echo " -a : Show all services" echo " -f : Follow log output" @@ -23,7 +24,7 @@ echo "${FILES[@]}" FOLLOW="" # Read params -while getopts "cvrahmf" opt; do +while getopts "cvrahmfs" opt; do case $opt in c) echo "Show Capif services" @@ -37,13 +38,17 @@ while getopts "cvrahmf" opt; do echo "Show register service" FILES+=("-f docker-compose-register.yml") ;; + s) + echo "Show Mock Server service" + FILES+=("-f docker-compose-mock-server.yml") + ;; m) echo "Show monitoring service" FILES+=("-f ../monitoring/docker-compose.yml") ;; a) echo "Show all services" - FILES=("-f docker-compose-capif.yml" -f "docker-compose-vault.yml" -f "docker-compose-register.yml" -f "../monitoring/docker-compose.yml") + FILES=("-f docker-compose-capif.yml" -f "docker-compose-vault.yml" -f "docker-compose-register.yml" -f "docker-compose-mock-server.yml" -f "../monitoring/docker-compose.yml") ;; f) echo "Setup follow logs" -- GitLab