From 81b883fd9e18ed6e8f2c9a799611a3e653b40b0c Mon Sep 17 00:00:00 2001 From: Alexandros Tzoumas Date: Tue, 25 Apr 2023 16:25:09 +0300 Subject: [PATCH] Libraries (#132) * update capif exposer onboarding function to conform with the flow of the latest CAPIF version. * initialize TSNManager + reformat with blackd * encapsulate TSNProfile, NetAppIdentifier, TSNProfileConfiguration classes into TSNManager * cleanup * Add basic usage examples of TSNManager * Update docstrings for TSNManager * Update the docstrings of TSNManager usage examples * publishing services gia capif provider connector class * rename class * add comments to help troubleshooting with Stavros * rollback to yesterdays version * Rename NetappTrafficIdentifier to TSNNetAppIdentifier * Add CLI command to get TSN profiles * add comments for troubleshooting * add comments for troubleshooting * add comments for troubleshooting * add comments for troubleshooting * remove comments, troubleshooting * rename capif cert pem * rename capif cert pem * rename TSNManager host and port attribute * replace example host to match docker config * reformat with black connector * remove merge artifact * bugfix key-value deduplication * simplify TSN example * enrich CLI commands for TSN * NetApp registration now conforms to the latest version of CAPIF * Extend ServiceDiscover with a new method get_access_token, that allows to retrieve an access token for a given api_name and aef_profile * add comments back in * add comments back in * Service Discovered, security context should be created only once for a given aef_id * remove properties from payloads that are not used * SDK now sends the CAPIF access token to NEF. SDK Libraries constructros dont expect NEF token as a parameter * update SDK Libraries constructors in the examples folder * update examples * print messages * bug fix * bug fix * fix self_signed_certificate warning * pass CERT_NONE to cert_reqs * fix verification of SSL to libraries * troubleshoot capif nef onboarding issue with Stavros * provide example for TSN * split examples service discoverer * update TSN communication to happen via CAPIF registrered endpoints * update NEF communication, send both NEF and CAPIF tokens * refactor examples for Providers (TSN and NEF). * bug fix on retrieving token from NEF * remove print statement * bug fix * add comments to the examples * TSN examples * update documentation * update version * bug fix on access token * bug fix on access token * bug fix when creating the capif_cert_server.pem, the file should always be overriden every time it is generated * merge * update version * delete not used method * TSN Manager now sends access tokens from CAPIF to the TSN API * fix header, use Bearer tag instead of Basic * CAPIF Logger class and example - first iteration * CAPIF Logger class * merge with master and update history.srt * add class CapifAuditor, update documentation and examples * remove python comments * update version to 1.0.3 --------- Co-authored-by: EVOLVED5G <84563256+EVOLVED5G@users.noreply.github.com> Co-authored-by: Paula Encinar <89458572+pencinarsanz-atos@users.noreply.github.com> Co-authored-by: NikolasGialitsis --- HISTORY.rst | 11 +- docs/source/libraries.rst | 21 +- evolved5g/__init__.py | 2 +- evolved5g/sdk.py | 735 +++++++++++++------- examples/capif_exposer_utils.py | 15 + examples/emulator_utils.py | 8 - examples/nef_logger_and_audit_example.py | 86 +++ examples/netapp_capif_connector_examples.py | 2 +- setup.cfg | 2 +- setup.py | 2 +- 10 files changed, 595 insertions(+), 289 deletions(-) create mode 100644 examples/nef_logger_and_audit_example.py diff --git a/HISTORY.rst b/HISTORY.rst index 245cb7d..f2bdeea 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,10 +2,19 @@ History ======= +------------------- +1.0.3 (2023-04-25) +------------------- +* New SDK Class CAPIFLogger, that allows a provider (like NEF Emulator) to save Log information, for example capture incoming requests and responses +* New SDK Class CAPIFAuditor, that allows a provider (like NEF Emulator) to query the Log, for example filter all the Log information for a given Network APP +* Added examples of usage for CAPIFLogger and CAPIFAuditor class in examples/nef_logger_and_audit_example.py +* At CAPIFProviderConnector class when using the publish_services() method, a copy of the published to CAPIF api is saved to the local certificates folder. This is useful if you want to retrieve the relevant CAPIF ids for this service like aef_id or api_id. + ------------------- 1.0.2 (2023-03-09) ------------------- -* Bug fix on minimum python version. Change it from 3.6 to 3.7. Installing the SDK in older ubuntu distribution raises an error at runtime requiring higher python version coming from Cookiecutter. +* Update TSN Manager class, apply security improvements. Now each request to the TSN API uses JWT Bearer authentication. Access tokens are retrieved via CAPIF. +* Bug fix on minimum python version. Change it from 3.6 to 3.7. Installing the SDK in older ubuntu distribution raises an error at runtime requiring higher python version coming from Cookiecutter. ------------------- 1.0.1 (2023-03-03) diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index 80eab37..7222df6 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -7,10 +7,12 @@ At the current release the SDK contains six classes * **LocationSubscriber**: allows you to track devices and retrieve updates about their location.You can use LocationSubscriber to create subscriptions, where each one of them can be used to track a device. * **QosAwareness**: allows you to request QoS from a set of standardized values for better service experience (Ex. TCP_BASED / LIVE Streaming / CONVERSATIONAL_VOICE etc). You can create subscriptions where each one of them has specific QoS parameters. A notification is sent back to the net-app if the QoS targets can no longer be full-filled. * **ConnectionMonitor**: allows you to monitor devices in the 5G Network. You can use this class to retrieve notifications by the 5G Network for individual devices when connection is lost (for example the user device has not been connected to the 5G network for the past 10 seconds) or when connection is alive. -* **TSNManager** allows NetApp developers to apply Time-Sensitive Networking (TSN) standards to time-sensitive NetApps. Allows the configuration of certain parameters in the underlying TSN infrastructure of the testbed. +* **TSNManager** allows Network App developers to apply Time-Sensitive Networking (TSN) standards to time-sensitive NetApps. Allows the configuration of certain parameters in the underlying TSN infrastructure of the testbed. * **CAPIFInvokerConnector** a low level class, that is used by the evolved-5G CLI in order to register NetApps to CAPIF -* **ServiceDiscoverer** allows NetApp developers to connect to CAPIF in order to discover services. It's also used internally within the SDK in order to get access token from CAPIF before interacting with services like NEF or CAPIF. +* **ServiceDiscoverer** allows Network App developers to connect to CAPIF in order to discover services. It's also used internally within the SDK in order to get access token from CAPIF before interacting with services like NEF or CAPIF. * **CAPIFProviderConnector** a low level class, that allows an Exposer (like the NEF emulator or the TSN emulator) to register to CAPIF +* **CAPIFLogger**, that allows an API provider (like NEF Emulator) to save Log information, for example capture incoming requests and responses +* **CAPIFAuditor**, that allows an API provider (like NEF Emulator) to query the Log, for example filter all the Log information for a given Network APP @@ -26,12 +28,17 @@ Have a look at the examples folder for code samples on how to use the SDK Librar * `CAPIFInvokerConnector example `_ -* `CAPIFProviderConnector example at and `_ +* `CAPIFProviderConnector example for NEF `_ +* `CAPIFProviderConnector example for TSN `_ * `ServiceDiscoverer example `_ * `TSNManager example `_ +* `CAPIFLogger example `_ + +* `CAPIFAuditor example `_ + Prerequisites / How to start ---------------------------- @@ -49,7 +56,7 @@ Make sure you have initiated the TSN server (See `here `_ for instructions), you have logged in to the interface, clicked on the map and have started the simulation. -Make sure that your NetApp has been registered and onboarded to CAPIF. If this process is completed, then in the specified folder the following files should be present +Make sure that your Network App has been registered and onboarded to CAPIF. If this process is completed, then in the specified folder the following files should be present - ca.crt - capif_api_details.json @@ -88,7 +95,7 @@ ConnectionMonitor Library Overview ################### -ConnectionMonitor library supports two events as described briefly above. The first event is the loss of connectivity event where the network detects that a UE is no longer reachable for either signalling or user plane communication. The NetApp may provide a Maximum Detection Time, which indicates the maximum period of time without any communication with the UE (after the UE is considered to be unreachable by the network). The respective monitoring type enumeration and the maximum detection time parameter are shown below: +ConnectionMonitor library supports two events as described briefly above. The first event is the loss of connectivity event where the network detects that a UE is no longer reachable for either signalling or user plane communication. The Network App may provide a Maximum Detection Time, which indicates the maximum period of time without any communication with the UE (after the UE is considered to be unreachable by the network). The respective monitoring type enumeration and the maximum detection time parameter are shown below: .. code-block:: console @@ -104,10 +111,10 @@ The second event is the ue reachability event where the network detects when the Prerequisite ################### -❗An important prerequisite for the loss of connectivity event (INFORM_WHEN_NOT_CONNECTED) is that while a NetApp successfully receives the callback notification from the NEF Emulator, subsequently NEF expects an ``HTTP Response`` with the ``JSON`` content shown below: +❗An important prerequisite for the loss of connectivity event (INFORM_WHEN_NOT_CONNECTED) is that while a Network App successfully receives the callback notification from the NEF Emulator, subsequently NEF expects an ``HTTP Response`` with the ``JSON`` content shown below: .. code-block:: console {"ack" : "TRUE"} -As a result, the developer should ensure that in the endpoint that is responsible for receiving the callback notifications (HTTP POST requests) from NEF, NetApp always returns the aforementioned acknowledgement, in ``JSON`` format. +As a result, the developer should ensure that in the endpoint that is responsible for receiving the callback notifications (HTTP POST requests) from NEF, Network App always returns the aforementioned acknowledgement, in ``JSON`` format. diff --git a/evolved5g/__init__.py b/evolved5g/__init__.py index 35c6739..7f8a722 100644 --- a/evolved5g/__init__.py +++ b/evolved5g/__init__.py @@ -1,7 +1,7 @@ """Top-level package for Evolved5G_CLI.""" __author__ = "EVOLVED5G project" -__version__ = "1.0.2" +__version__ = "1.0.3" # Uncomment next lines to give direct import access to modules # from . import cli diff --git a/evolved5g/sdk.py b/evolved5g/sdk.py index 93014ed..57efda3 100644 --- a/evolved5g/sdk.py +++ b/evolved5g/sdk.py @@ -5,6 +5,7 @@ from evolved5g import swagger_client from abc import ABC, abstractmethod from enum import Enum +from dataclasses import dataclass from evolved5g.swagger_client import ( MonitoringEventAPIApi, MonitoringEventSubscriptionCreate, @@ -41,17 +42,18 @@ class MonitoringSubscriber(ABC): def __init__( - self, - host: str, - nef_bearer_access_token: str, - folder_path_for_certificates_and_capif_api_key: str, - capif_host: str, - capif_https_port: int, + self, + host: str, + nef_bearer_access_token: str, + folder_path_for_certificates_and_capif_api_key: str, + capif_host: str, + capif_https_port: int, ): configuration = swagger_client.Configuration() configuration.verify_ssl = False configuration.host = host - service_discoverer = ServiceDiscoverer(folder_path_for_certificates_and_capif_api_key, capif_host, capif_https_port) + service_discoverer = ServiceDiscoverer(folder_path_for_certificates_and_capif_api_key, capif_host, + capif_https_port) api_name = "/nef/api/v1/3gpp-monitoring-event/" configuration.available_endpoints = { "MONITORING_SUBSCRIPTIONS": service_discoverer.retrieve_specific_resource_name(api_name, @@ -60,20 +62,26 @@ def __init__( "MONITORING_SUBSCRIPTION_SINGLE"), } api_resource_description = service_discoverer.retrieve_api_description_by_name(api_name) - configuration.access_token = nef_bearer_access_token + "," + service_discoverer.get_access_token(api_name,api_resource_description["apiId"],api_resource_description["aefProfiles"][0]["aefId"]) + configuration.access_token = nef_bearer_access_token + "," + service_discoverer.get_access_token(api_name, + api_resource_description[ + "apiId"], + api_resource_description[ + "aefProfiles"][ + 0][ + "aefId"]) api_client = swagger_client.ApiClient(configuration=configuration) self.monitoring_event_api = MonitoringEventAPIApi(api_client) self.cell_api = CellsApi(api_client) def create_subscription_request( - self, - monitoring_type, - external_id, - notification_destination, - maximum_number_of_reports, - monitor_expire_time, - maximum_detection_time, - reachability_type, + self, + monitoring_type, + external_id, + notification_destination, + maximum_number_of_reports, + monitor_expire_time, + maximum_detection_time, + reachability_type, ) -> MonitoringEventSubscriptionCreate: return MonitoringEventSubscriptionCreate( external_id, @@ -99,7 +107,7 @@ def get_all_subscriptions(self, netapp_id: str, skip: int = 0, limit: int = 100) ) def get_subscription( - self, netapp_id: str, subscription_id: str + self, netapp_id: str, subscription_id: str ) -> MonitoringEventSubscription: """ Gets subscription by id @@ -125,12 +133,12 @@ def delete_subscription(self, netapp_id: str, subscription_id: str): class LocationSubscriber(MonitoringSubscriber): def __init__( - self, - nef_url: str, - nef_bearer_access_token: str, - folder_path_for_certificates_and_capif_api_key: str, - capif_host: str, - capif_https_port: int, + self, + nef_url: str, + nef_bearer_access_token: str, + folder_path_for_certificates_and_capif_api_key: str, + capif_host: str, + capif_https_port: int, ): """ Initializes class LocationSubscriber. @@ -152,7 +160,7 @@ def __get_monitoring_type(self): return "LOCATION_REPORTING" def get_location_information( - self, netapp_id: str, external_id + self, netapp_id: str, external_id ) -> MonitoringEventReport: """ Returns the location of a specific device. @@ -163,8 +171,8 @@ def get_location_information( # create a dummy expiration time. Since we are requesting for only 1 report, we will get the location information back instantly monitor_expire_time = ( - datetime.datetime.utcnow() + datetime.timedelta(minutes=1) - ).isoformat() + "Z" + datetime.datetime.utcnow() + datetime.timedelta(minutes=1) + ).isoformat() + "Z" body = self.create_subscription_request( self.__get_monitoring_type(), external_id, @@ -190,12 +198,12 @@ def get_coordinates_of_cell(self, cell_id: str) -> Cell: return self.cell_api.read_cell_api_v1_cells_cell_id_get(cell_id) def create_subscription( - self, - netapp_id: str, - external_id, - notification_destination, - maximum_number_of_reports, - monitor_expire_time, + self, + netapp_id: str, + external_id, + notification_destination, + maximum_number_of_reports, + monitor_expire_time, ) -> MonitoringEventSubscription: """ Creates a subscription that will be used to retrieve Location information about a device. @@ -223,13 +231,13 @@ def create_subscription( return response def update_subscription( - self, - netapp_id: str, - subscription_id: str, - external_id, - notification_destination, - maximum_number_of_reports, - monitor_expire_time, + self, + netapp_id: str, + subscription_id: str, + external_id, + notification_destination, + maximum_number_of_reports, + monitor_expire_time, ) -> MonitoringEventSubscription: """ Creates a subscription that will be used to retrieve Location information about a device. @@ -268,12 +276,12 @@ class MonitoringType(Enum): INFORM_WHEN_NOT_CONNECTED = 2 def __init__( - self, - nef_url: str, - nef_bearer_access_token:str, - folder_path_for_certificates_and_capif_api_key: str, - capif_host: str, - capif_https_port: int, + self, + nef_url: str, + nef_bearer_access_token: str, + folder_path_for_certificates_and_capif_api_key: str, + capif_host: str, + capif_https_port: int, ): """ Initializes class ConnectionMonitor. @@ -305,14 +313,14 @@ def __get_monitoring_type(self, monitoring_type: MonitoringType): return "LOSS_OF_CONNECTIVITY" def create_subscription( - self, - netapp_id: str, - external_id, - notification_destination, - monitoring_type: MonitoringType, - wait_time_before_sending_notification_in_seconds: int, - maximum_number_of_reports, - monitor_expire_time, + self, + netapp_id: str, + external_id, + notification_destination, + monitoring_type: MonitoringType, + wait_time_before_sending_notification_in_seconds: int, + maximum_number_of_reports, + monitor_expire_time, ) -> MonitoringEventSubscription: """ Creates a subscription that will be used to track the Network Connectivity about a device. @@ -349,15 +357,15 @@ def create_subscription( return response def update_subscription( - self, - netapp_id: str, - subscription_id: str, - external_id, - notification_destination, - monitoring_type: MonitoringType, - wait_time_before_sending_notification_in_seconds: int, - maximum_number_of_reports, - monitor_expire_time, + self, + netapp_id: str, + subscription_id: str, + external_id, + notification_destination, + monitoring_type: MonitoringType, + wait_time_before_sending_notification_in_seconds: int, + maximum_number_of_reports, + monitor_expire_time, ) -> MonitoringEventSubscription: """ Creates a subscription that will be used to retrieve Location information about a device. @@ -497,12 +505,12 @@ def get_reporting_configuration(self): return self.repetition_period_in_seconds def __init__( - self, - nef_url: str, - nef_bearer_access_token: str, - folder_path_for_certificates_and_capif_api_key: str, - capif_host: str, - capif_https_port: int, + self, + nef_url: str, + nef_bearer_access_token: str, + folder_path_for_certificates_and_capif_api_key: str, + capif_host: str, + capif_https_port: int, ): """ Initializes class QosAwareness. @@ -537,19 +545,25 @@ def __init__( ), } api_resource_description = service_discoverer.retrieve_api_description_by_name(api_name) - configuration.access_token =nef_bearer_access_token + "," + service_discoverer.get_access_token(api_name,api_resource_description["apiId"],api_resource_description["aefProfiles"][0]["aefId"]) + configuration.access_token = nef_bearer_access_token + "," + service_discoverer.get_access_token(api_name, + api_resource_description[ + "apiId"], + api_resource_description[ + "aefProfiles"][ + 0][ + "aefId"]) api_client = swagger_client.ApiClient(configuration=configuration) self.qos_api = SessionWithQoSAPIApi(api_client) def create_subscription_request( - self, - equipment_network_identifier: str, - network_identifier: NetworkIdentifier, - notification_destination: str, - qos_reference: int, - alt_qo_s_references, - usage_threshold: UsageThreshold, - qos_mon_info, + self, + equipment_network_identifier: str, + network_identifier: NetworkIdentifier, + notification_destination: str, + qos_reference: int, + alt_qo_s_references, + usage_threshold: UsageThreshold, + qos_mon_info, ) -> AsSessionWithQoSSubscriptionCreate: ip4_address_value = ( equipment_network_identifier @@ -588,13 +602,13 @@ def create_subscription_request( ) def create_non_guaranteed_bit_rate_subscription( - self, - netapp_id, - equipment_network_identifier: str, - network_identifier: NetworkIdentifier, - notification_destination: str, - non_gbr_qos_reference: NonGBRQosReference, - usage_threshold: UsageThreshold, + self, + netapp_id, + equipment_network_identifier: str, + network_identifier: NetworkIdentifier, + notification_destination: str, + non_gbr_qos_reference: NonGBRQosReference, + usage_threshold: UsageThreshold, ) -> AsSessionWithQoSSubscription: """ Initializes a Non Guaranteed Bit Rate (NGBR) Quality of Service (QoS) subscription. @@ -628,14 +642,14 @@ def create_non_guaranteed_bit_rate_subscription( return response def update_non_guaranteed_bit_rate_subscription( - self, - netapp_id: str, - subscription_id: str, - equipment_network_identifier: str, - network_identifier: NetworkIdentifier, - notification_destination: str, - non_gbr_qos_reference: NonGBRQosReference, - usage_threshold: UsageThreshold, + self, + netapp_id: str, + subscription_id: str, + equipment_network_identifier: str, + network_identifier: NetworkIdentifier, + notification_destination: str, + non_gbr_qos_reference: NonGBRQosReference, + usage_threshold: UsageThreshold, ) -> AsSessionWithQoSSubscription: """ Updates a given subscription. @@ -668,16 +682,16 @@ def update_non_guaranteed_bit_rate_subscription( ) def create_guaranteed_bit_rate_subscription( - self, - netapp_id, - equipment_network_identifier, - network_identifier, - notification_destination: str, - gbr_qos_reference: GBRQosReference, - usage_threshold: UsageThreshold, - qos_monitoring_parameter: QosMonitoringParameter, - threshold: int, - reporting_mode: GuaranteedBitRateReportingMode, + self, + netapp_id, + equipment_network_identifier, + network_identifier, + notification_destination: str, + gbr_qos_reference: GBRQosReference, + usage_threshold: UsageThreshold, + qos_monitoring_parameter: QosMonitoringParameter, + threshold: int, + reporting_mode: GuaranteedBitRateReportingMode, ) -> AsSessionWithQoSSubscription: """ @@ -723,11 +737,11 @@ def create_guaranteed_bit_rate_subscription( return response def __create_gbr_request_qo_parameters( - self, - gbr_qos_reference, - qos_monitoring_parameter, - threshold, - reporting_mode: GuaranteedBitRateReportingMode, + self, + gbr_qos_reference, + qos_monitoring_parameter, + threshold, + reporting_mode: GuaranteedBitRateReportingMode, ): # Contains the remaining Guaranted Qos references, as fallback alt_qo_s_references = [] @@ -756,7 +770,7 @@ def __create_gbr_request_qo_parameters( rep_period = None if isinstance( - reporting_mode, QosAwareness.EventTriggeredReportingConfiguration + reporting_mode, QosAwareness.EventTriggeredReportingConfiguration ): wait_time = reporting_mode.get_reporting_configuration() else: @@ -774,17 +788,17 @@ def __create_gbr_request_qo_parameters( return alt_qo_s_references, qos_monitoring_info def update_guaranteed_bit_rate_subscription( - self, - netapp_id: str, - subscription_id: str, - equipment_network_identifier: str, - network_identifier: NetworkIdentifier, - notification_destination: str, - gbr_qos_reference: GBRQosReference, - usage_threshold: UsageThreshold, - qos_monitoring_parameter: QosMonitoringParameter, - threshold: int, - reporting_mode: GuaranteedBitRateReportingMode, + self, + netapp_id: str, + subscription_id: str, + equipment_network_identifier: str, + network_identifier: NetworkIdentifier, + notification_destination: str, + gbr_qos_reference: GBRQosReference, + usage_threshold: UsageThreshold, + qos_monitoring_parameter: QosMonitoringParameter, + threshold: int, + reporting_mode: GuaranteedBitRateReportingMode, ) -> AsSessionWithQoSSubscription: """ Updates a given subscription. @@ -828,7 +842,7 @@ def update_guaranteed_bit_rate_subscription( ) def get_all_subscriptions( - self, netapp_id: str + self, netapp_id: str ) -> List[AsSessionWithQoSSubscription]: """ Reads all active subscriptions @@ -841,7 +855,7 @@ def get_all_subscriptions( ) def get_subscription( - self, netapp_id: str, subscription_id: str + self, netapp_id: str, subscription_id: str ) -> AsSessionWithQoSSubscription: """ @@ -871,22 +885,22 @@ class CAPIFInvokerConnector: """ def __init__( - self, - folder_to_store_certificates: str, - capif_host: str, - capif_http_port: str, - capif_https_port: str, - capif_netapp_username, - capif_netapp_password: str, - capif_callback_url: str, - description: str, - csr_common_name: str, - csr_organizational_unit: str, - csr_organization: str, - crs_locality: str, - csr_state_or_province_name, - csr_country_name, - csr_email_address, + self, + folder_to_store_certificates: str, + capif_host: str, + capif_http_port: str, + capif_https_port: str, + capif_netapp_username, + capif_netapp_password: str, + capif_callback_url: str, + description: str, + csr_common_name: str, + csr_organizational_unit: str, + csr_organization: str, + crs_locality: str, + csr_state_or_province_name, + csr_country_name, + csr_email_address, ): """ @@ -918,15 +932,13 @@ def __init__( self.capif_http_url = "http://" + capif_host.strip() + "/" else: self.capif_http_url = ( - "http://" + capif_host.strip() + ":" + capif_http_port.strip() + "/" + "http://" + capif_host.strip() + ":" + capif_http_port.strip() + "/" ) if len(capif_https_port) == 0 or int(capif_https_port) == 443: self.capif_https_url = "https://" + capif_host.strip() + "/" else: - self.capif_https_url = ( - "https://" + capif_host.strip() + ":" + capif_https_port.strip() + "/" - ) + self.capif_https_url = "https://" + capif_host.strip() + ":" + capif_https_port.strip() + "/" self.capif_callback_url = self.__add_trailing_slash_to_url_if_missing( capif_callback_url.strip() @@ -1047,7 +1059,7 @@ def __save_capif_ca_root_file_and_get_auth_token(self, role): return response_payload["access_token"] def __onboard_netapp_to_capif_and_create_the_signed_certificate( - self, public_key, capif_onboarding_url, capif_access_token + self, public_key, capif_onboarding_url, capif_access_token ): url = self.capif_https_url + capif_onboarding_url payload_dict = { @@ -1090,7 +1102,7 @@ def __onboard_netapp_to_capif_and_create_the_signed_certificate( def __write_to_file(self, csr_common_name, api_invoker_id, discover_services_url): with open( - self.folder_to_store_certificates + "capif_api_details.json", "w" + self.folder_to_store_certificates + "capif_api_details.json", "w" ) as outfile: json.dump( { @@ -1108,21 +1120,21 @@ class CAPIFProviderConnector: """ def __init__( - self, - certificates_folder: str, - description: str, - capif_host: str, - capif_http_port: str, - capif_https_port: str, - capif_netapp_username, - capif_netapp_password: str, - csr_common_name: str, - csr_organizational_unit: str, - csr_organization: str, - crs_locality: str, - csr_state_or_province_name, - csr_country_name, - csr_email_address, + self, + certificates_folder: str, + description: str, + capif_host: str, + capif_http_port: str, + capif_https_port: str, + capif_netapp_username, + capif_netapp_password: str, + csr_common_name: str, + csr_organizational_unit: str, + csr_organization: str, + crs_locality: str, + csr_state_or_province_name, + csr_country_name, + csr_email_address, ): """ :param certificates_folder: The folder where certificates will be created and stored. @@ -1152,14 +1164,14 @@ def __init__( self.capif_http_url = "http://" + capif_host.strip() + "/" else: self.capif_http_url = ( - "http://" + capif_host.strip() + ":" + capif_http_port.strip() + "/" + "http://" + capif_host.strip() + ":" + capif_http_port.strip() + "/" ) if len(capif_https_port) == 0 or int(capif_https_port) == 443: self.capif_https_url = "https://" + capif_host.strip() + "/" else: self.capif_https_url = ( - "https://" + capif_host.strip() + ":" + capif_https_port.strip() + "/" + "https://" + capif_host.strip() + ":" + capif_https_port.strip() + "/" ) self.capif_host = capif_host.strip() @@ -1201,7 +1213,7 @@ def __create_private_and_public_keys(self, api_prov_func_role) -> bytes: :return: The contents of the public key """ private_key_path = ( - self.certificates_folder + api_prov_func_role + "_private_key.key" + self.certificates_folder + api_prov_func_role + "_private_key.key" ) csr_file_path = self.certificates_folder + api_prov_func_role + "_public.csr" @@ -1287,7 +1299,6 @@ def __register_to_capif(self, role): payload["password"] = self.capif_netapp_password payload["role"] = role payload["description"] = self.description - # TODO: ASK STAVROS. THIS INFORMATION IS STORED AT CAPIF users collection. Is it used somewhere else at the capif? Notice it does not have postfixes "AEF/APF/AMF" payload["cn"] = self.csr_common_name response = requests.request( @@ -1327,18 +1338,18 @@ def __write_to_file(self, onboarding_response, capif_registration_id, publish_ur for func_provile in onboarding_response["apiProvFuncs"]: with open( - self.certificates_folder - + "dummy_" - + func_provile["apiProvFuncRole"].lower() - + ".crt", - "wb", + self.certificates_folder + + "dummy_" + + func_provile["apiProvFuncRole"].lower() + + ".crt", + "wb", ) as certification_file: certification_file.write( bytes(func_provile["regInfo"]["apiProvCert"], "utf-8") ) with open( - self.certificates_folder + "capif_provider_details.json", "w" + self.certificates_folder + "capif_provider_details.json", "w" ) as outfile: data = { "capif_registration_id": capif_registration_id, @@ -1370,13 +1381,16 @@ def register_and_onboard_provider(self) -> None: onboarding_response, capif_registration_id, ccf_publish_url ) - def publish_services(self, service_api_description_json_full_path) -> None: + def publish_services(self, service_api_description_json_full_path) -> dict: """ - :param service_api_description_json_full_path: The full path fo the service_api_description.json that contains - the endpoints that will be published + :param service_api_description_json_full_path: The full path fo the service_api_description.json that contains + the endpoints that will be published + :return: The published services dictionary that was saved in CAPIF + """ + with open( - self.certificates_folder + "capif_provider_details.json", "r" + self.certificates_folder + "capif_provider_details.json", "r" ) as openfile: file = json.load(openfile) publish_url = file["publish_url"] @@ -1392,23 +1406,25 @@ def publish_services(self, service_api_description_json_full_path) -> None: for profile in data["aefProfiles"]: profile["aefId"] = AEF_api_prov_func_id - response = requests.request( - "POST", - url, - headers={"Content-Type": "application/json"}, - data=json.dumps(data), - cert=( - # TODO:Ask Stavros should this be named like APF_dummy.crt or like dummy_apf.crt? Does the naming matters? Also we are passing the APF and not the AMF or the AEF keys? - # When should AMF or AEF be passed? At which scenarios? - # Is it fine that we have hardcoded these? - self.certificates_folder + "dummy_apf.crt", - self.certificates_folder + "APF_private_key.key", - ), - verify=self.certificates_folder + "ca.crt", - ) - response.raise_for_status() - response_payload = json.loads(response.text) - return response_payload["apiId"] + response = requests.request( + "POST", + url, + headers={"Content-Type": "application/json"}, + data=json.dumps(data), + cert=( + self.certificates_folder + "dummy_apf.crt", + self.certificates_folder + "APF_private_key.key", + ), + verify=self.certificates_folder + "ca.crt", + ) + response.raise_for_status() + capif_response = response.text + + file_name = os.path.basename(service_api_description_json_full_path) + with open(self.certificates_folder + "CAPIF_" + file_name, "w") as outfile: + outfile.write(capif_response) + + return json.loads(capif_response) class ServiceDiscoverer: @@ -1416,10 +1432,10 @@ class ServiceDiscovererException(Exception): pass def __init__( - self, - folder_path_for_certificates_and_api_key: str, - capif_host: str, - capif_https_port: int, + self, + folder_path_for_certificates_and_api_key: str, + capif_host: str, + capif_https_port: int, ): self.capif_host = capif_host self.capif_https_port = capif_https_port @@ -1429,7 +1445,7 @@ def __init__( self.capif_api_details = self.__load_netapp_api_details() self.signed_key_crt_path = ( self.folder_to_store_certificates_and_api_key - + self.capif_api_details["csr_common_name"]+ ".crt" + + self.capif_api_details["csr_common_name"] + ".crt" ) self.private_key_path = self.folder_to_store_certificates_and_api_key + "private.key" self.ca_root_path = self.folder_to_store_certificates_and_api_key + "ca.crt" @@ -1449,7 +1465,7 @@ def _add_trailing_slash_to_url_if_missing(self, url): url = url + "/" return url - def get_access_token(self,api_name,api_id,aef_id): + def get_access_token(self, api_name, api_id, aef_id): """ :param api_name: The api id name is returned by discover services :param api_id: The api id that is returned by discover services @@ -1457,27 +1473,27 @@ def get_access_token(self,api_name,api_id,aef_id): :return: The access token (jwt) """ if not self.__aef_id_already_registered(aef_id): - self.__register_security_service(api_id,aef_id) + self.__register_security_service(api_id, aef_id) self.__save_aef_id_to_already_registered_cached_list(aef_id) - token_dic = self.__get_security_token(api_name,aef_id) + token_dic = self.__get_security_token(api_name, aef_id) return token_dic["access_token"] - def __aef_id_already_registered(self,aef_id): - return "registered_aef_ids" in self.capif_api_details and \ + def __aef_id_already_registered(self, aef_id): + return "registered_aef_ids" in self.capif_api_details and \ aef_id in self.capif_api_details["registered_aef_ids"] - def __save_aef_id_to_already_registered_cached_list(self,aef_id): + def __save_aef_id_to_already_registered_cached_list(self, aef_id): if "registered_aef_ids" not in self.capif_api_details: - self.capif_api_details["registered_aef_ids"] =[] + self.capif_api_details["registered_aef_ids"] = [] self.capif_api_details["registered_aef_ids"].append(aef_id) with open( self.folder_to_store_certificates_and_api_key + "capif_api_details.json", "w" ) as outfile: - json.dump(self.capif_api_details,outfile) + json.dump(self.capif_api_details, outfile) - def __register_security_service(self, api_id, aef_id): + def __register_security_service(self, api_id, aef_id): """ :param api_id: The api id that is returned by discover services @@ -1487,7 +1503,6 @@ def __register_security_service(self, api_id, aef_id): url = "https://{}/capif-security/v1/trustedInvokers/{}".format(self.capif_host, self.capif_api_details["api_invoker_id"]) - payload = { "securityInfo": [ { @@ -1515,20 +1530,21 @@ def __register_security_service(self, api_id, aef_id): response.raise_for_status() response_payload = response.json() - def __get_security_token(self,api_name,aef_id ): + def __get_security_token(self, api_name, aef_id): """ :param api_name: The api id name is returned by discover services :param aef_id: The relevant aef_id that is returned by discover services :return: The access token (jwt) """ - url = "https://{}/capif-security/v1/securities/{}/token".format(self.capif_host,self.capif_api_details["api_invoker_id"] ) + url = "https://{}/capif-security/v1/securities/{}/token".format(self.capif_host, + self.capif_api_details["api_invoker_id"]) - payload= { + payload = { "grant_type": "client_credentials", "client_id": self.capif_api_details["api_invoker_id"], "client_secret": "string", - "scope": "3gpp#"+aef_id+":"+api_name + "scope": "3gpp#" + aef_id + ":" + api_name } headers = { 'Content-Type': 'application/x-www-form-urlencoded', @@ -1552,7 +1568,6 @@ def discover_service_apis(self): self.capif_api_details["api_invoker_id"], ) - response = requests.request( "GET", url, @@ -1566,7 +1581,7 @@ def discover_service_apis(self): response_payload = json.loads(response.text) return response_payload - def retrieve_api_description_by_name(self,api_name): + def retrieve_api_description_by_name(self, api_name): capif_apifs = self.discover_service_apis() endpoints = list( filter(lambda api: api["apiName"] == api_name, capif_apifs["serviceAPIDescriptions"]) @@ -1625,27 +1640,35 @@ class TSNManager: """ def __init__( - self, - folder_path_for_certificates_and_capif_api_key:str, - capif_host: str, - capif_https_port: int, - https: bool, - tsn_host: str, - tsn_port: int, + self, + folder_path_for_certificates_and_capif_api_key: str, + capif_host: str, + capif_https_port: int, + https: bool, + tsn_host: str, + tsn_port: int, ) -> None: self.folder_path_for_certificates_and_capif_api_key = os.path.join( folder_path_for_certificates_and_capif_api_key.strip(), "" ) self.api_name = "/tsn/api/" - self.service_discoverer = ServiceDiscoverer(self.folder_path_for_certificates_and_capif_api_key,capif_host, capif_https_port) + self.service_discoverer = ServiceDiscoverer(self.folder_path_for_certificates_and_capif_api_key, capif_host, + capif_https_port) + api_resource_description = self.service_discoverer.retrieve_api_description_by_name(self.api_name) + self.access_token = self.service_discoverer.get_access_token(self.api_name, api_resource_description["apiId"], + api_resource_description["aefProfiles"][0][ + "aefId"]) + self.headers_auth = { + "Accept": "application/json", + 'Authorization': 'Bearer ' + self.access_token + } self.api_invoker_id = self.service_discoverer.get_api_invoker_id() self.url_prefix = "{protocol}://{host}:{port}".format( - protocol="https" if https else "http", - host=tsn_host, - port=tsn_port - ) - + protocol="https" if https else "http", + host=tsn_host, + port=tsn_port + ) class TSNNetappIdentifier: def __init__(self, netapp_name: str): @@ -1653,17 +1676,14 @@ def __init__(self, netapp_name: str): self.__identifier = self.__generate_random_identifier() def __generate_random_identifier(self): - - return "{netapp_name}_{random_uuid}".format( - netapp_name=self.netapp_name, random_uuid=uuid4().hex - ) + return "{netapp_name}_{random_uuid}".format( + netapp_name=self.netapp_name, random_uuid=uuid4().hex + ) @property def value(self): return self.__identifier - - class TSNProfile: def __init__(self, tsn_manager, profile_name): self.tsn_manager = tsn_manager @@ -1673,8 +1693,8 @@ def __init__(self, tsn_manager, profile_name): class TSNProfileConfiguration: def __init__(self, parameters_dict): for ( - profile_parameter_name, - profile_parameter_value, + profile_parameter_name, + profile_parameter_value, ) in parameters_dict.items(): setattr(self, profile_parameter_name, profile_parameter_value) @@ -1682,7 +1702,7 @@ def get_profile_configuration_parameters(self): return vars(self) def get_configuration_for_tsn_profile( - self, + self, ) -> TSNProfileConfiguration: """ Returns the configuration parameters of the selected time-sensitive networking (TSN) profile. @@ -1690,12 +1710,12 @@ def get_configuration_for_tsn_profile( :return: the default TSN profile configuration """ - url =self.tsn_manager.url_prefix +\ - self.tsn_manager.service_discoverer.\ - retrieve_specific_resource_name(self.tsn_manager.api_name, "TSN_DETAIL_PROFILE").\ - format(profileName=self.name) + url = self.tsn_manager.url_prefix + \ + self.tsn_manager.service_discoverer. \ + retrieve_specific_resource_name(self.tsn_manager.api_name, "TSN_DETAIL_PROFILE"). \ + format(profileName=self.name) - response = requests.get(url=url, headers={"Accept": "application/json"}) + response = requests.get(url=url, headers=self.tsn_manager.headers_auth) response.raise_for_status() parameters_dict = json.loads(response.text)[self.name] return self.TSNProfileConfiguration(parameters_dict) @@ -1707,11 +1727,11 @@ def get_tsn_profiles(self) -> [TSNProfile]: :return: a list of TSN profiles. Each TSN profile is a TSNProfile class. """ - url =self.url_prefix+ self.service_discoverer.\ - retrieve_specific_resource_name(self.api_name, "TSN_LIST_PROFILES").\ + url = self.url_prefix + self.service_discoverer. \ + retrieve_specific_resource_name(self.api_name, "TSN_LIST_PROFILES"). \ format(scsAsId=self.api_invoker_id) - response = requests.get(url=url, headers={"Accept": "application/json"}) + response = requests.get(url=url, headers=self.headers_auth) response.raise_for_status() response_dict = json.loads(response.text) return [ @@ -1720,9 +1740,9 @@ def get_tsn_profiles(self) -> [TSNProfile]: ] def apply_tsn_profile_to_netapp( - self, - tsn_netapp_identifier: TSNNetappIdentifier, - profile: TSNProfile, + self, + tsn_netapp_identifier: TSNNetappIdentifier, + profile: TSNProfile, ) -> str: """ Applies the time-sensitive networking (TSN) profile to the NetApp specified by @@ -1737,21 +1757,21 @@ def apply_tsn_profile_to_netapp( "profile": profile.name, "overrides": {}, } - url = self.url_prefix+ self.service_discoverer.retrieve_specific_resource_name(self.api_name, "TSN_APPLY_CONFIGURATION") - + url = self.url_prefix + self.service_discoverer.retrieve_specific_resource_name(self.api_name, + "TSN_APPLY_CONFIGURATION") response = requests.post( - url=url, json=data, headers={"Content-type": "application/json"} + url=url, json=data, headers=self.headers_auth ) response.raise_for_status() response = json.loads(response.text) return response["token"] def apply_profile_with_overriden_parameters_to_netapp( - self, - tsn_netapp_identifier: TSNNetappIdentifier, - base_profile: TSNProfile, - modified_params: dict, + self, + tsn_netapp_identifier: TSNNetappIdentifier, + base_profile: TSNProfile, + modified_params: dict, ) -> str: """ Overrides the default parameters of the time-sensitive networking (TSN) profile, and applies it to the NetApp @@ -1769,36 +1789,25 @@ def apply_profile_with_overriden_parameters_to_netapp( profile=base_profile, ) - - profile_base_params = ( - base_profile.get_configuration_for_tsn_profile().get_profile_configuration_parameters() - ) - for param_to_override, new_param_value in modified_params.items(): - try: - _ = profile_base_params[param_to_override] - except KeyError: - warnings.warn( - f'parameter value of "{param_to_override}" has no effect since it is not a configuration parameter ' - f'of the base profile "{base_profile.name}".\nOverridable parameters are ' - f'the following [{",".join(profile_base_params.keys())}]' - ) - data = { "identifier": tsn_netapp_identifier.value, "profile": base_profile.name, "overrides": modified_params, } - url = self.url_prefix+ self.service_discoverer.retrieve_specific_resource_name(self.api_name, "TSN_APPLY_CONFIGURATION") + url = self.url_prefix + self.service_discoverer.retrieve_specific_resource_name(self.api_name, + "TSN_APPLY_CONFIGURATION") response = requests.post( - url=url, json=data, headers={"Content-type": "application/json"} + url=url, + json=data, + headers=self.headers_auth ) response.raise_for_status() response = json.loads(response.text) return response["token"] def clear_profile_for_tsn_netapp_identifier( - self, tsn_netapp_identifier: TSNNetappIdentifier, clearance_token: str + self, tsn_netapp_identifier: TSNNetappIdentifier, clearance_token: str ) -> None: """ Disables a previously applied configuration for the selected NetApp @@ -1808,14 +1817,202 @@ def clear_profile_for_tsn_netapp_identifier( :param clearance_token: used to clear the applied TSN configuration from the NetApp """ - url = self.url_prefix+ self.service_discoverer.retrieve_specific_resource_name(self.api_name, "TSN_CLEAR_CONFIGURATION") + url = self.url_prefix + self.service_discoverer.retrieve_specific_resource_name(self.api_name, + "TSN_CLEAR_CONFIGURATION") data = { "identifier": tsn_netapp_identifier.value, "token": clearance_token, } response = requests.post( - url=url, json=data, headers={"Content-type": "application/json"} + url=url, json=data, headers=self.headers_auth ) response.raise_for_status() assert "success" in json.loads(response.text)["message"] + + +class CAPIFLogCommon(ABC): + def __init__(self, certificates_folder, + capif_host, + capif_https_port): + + self.certificates_folder = os.path.join(certificates_folder.strip(), "") + self.capif_https_url = "" + if len(capif_https_port) == 0 or int(capif_https_port) == 443: + self.capif_https_url = "https://" + capif_host.strip() + "/" + else: + self.capif_https_url = "https://" + capif_host.strip() + ":" + capif_https_port.strip() + "/" + + with open( + self.certificates_folder + "capif_provider_details.json", "r" + ) as openfile: + self.capif_provider_details = json.load(openfile) + self.aef_id = self.capif_provider_details["AEF_api_prov_func_id"] + + def get_capif_service_description(self, capif_service_api_description_json_full_path): + """ + Use this method to read the api_id of your service from the relevant file or other relevant information + + :param capif_service_api_description_json_full_path: + This file is generated when you register your Provider to CAPIF. It is stored inside your certificate folder. + :return: The service description json that is stored in CAPIF + """ + with open( + capif_service_api_description_json_full_path, "r" + ) as openfile: + return json.load(openfile) + +class CAPIFLogger(CAPIFLogCommon): + + def __init__(self, + certificates_folder, + capif_host, + capif_https_port): + + """ + :param certificates_folder: The certificated folder you used during registration of the Provider to CAPIF + :param capif_host: The CAPIF host name + :param capif_https_port: The CAPIF https port + """ + super().__init__(certificates_folder,capif_host,capif_https_port) + self.capif_logger_url = self.capif_https_url + "api-invocation-logs/v1/" + self.aef_id + "/logs" + + @dataclass + class LogEntry: + """ + A class representing a LogEntry that will be saved to CAPIF Invocation logs. + + Attributes: + apiId (str): The ID of the API invoked. + apiVersion (str): The version of the API that was invoked. + apiName (str): The name of the API. + resourceName (str): The name of the resource being invoked + uri (str): Full URI of the request. + protocol (str): The protocol used for the request (ex. HTTP_1_1) + invocationLatency (int): The time taken to process the request, in milliseconds. + invocationTime (datetime): Date on which the request was invoked. + operation (str): The HTTP operation being performed (Ex. GET,POST,PUT,DELETE) + result (int): The HTTP status code of the results (ex. 200) + inputParameters (dict): The input parameters for the request. + outputParameters (dict): The output / response parameters + """ + apiId: str + apiVersion: str + apiName: str + resourceName: str + uri: str + protocol: str + invocationLatency: int + invocationTime: datetime + operation: str + result: int + inputParameters: dict + outputParameters: dict + + + + + + def save_log(self, api_invoker_id, log_entries: List[LogEntry]): + + payload = { + "aefId": self.aef_id, + "apiInvokerId": api_invoker_id, + "logs": list(map(lambda logentry: logentry.__dict__, log_entries)), + "supportedFeatures": "fffffff" + } + + headers = { + 'Content-Type': 'application/json' + } + + response = requests.request("POST", self.capif_logger_url, + headers=headers, + data=json.dumps(payload), + cert=( + self.certificates_folder + "dummy_aef.crt", + self.certificates_folder + "AEF_private_key.key", + ), + verify=self.certificates_folder + 'ca.crt') + response.raise_for_status() + response_payload = json.loads(response.text) + + return response_payload + + + + +class CAPIFAuditor(CAPIFLogCommon): + + def __init__(self, + certificates_folder, + capif_host, + capif_https_port): + """ + :param certificates_folder: The certificated folder you used during registration of the Provider to CAPIF + :param capif_host: The CAPIF host name + :param capif_https_port: The CAPIF https port + """ + super().__init__(certificates_folder,capif_host,capif_https_port) + self.capif_query_log_url = self.capif_https_url + "logs/v1/apiInvocationLogs" + + def query_log(self, api_invoker_id=None, time_start=None, time_end=None, api_id=None, + api_name=None, api_version=None, result=None, resource_name=None, protocol=None, + operation=None): + + """ + :param api_invoker_id: + :param time_start: # e.g. 2022-10-24T00:00:00.000Z + :param time_end: # e.g. 2022-10-25T00:00:00.000Z + :param api_id: # e.g. f7ba97e8f08a7f53365ba81be60a0c + :param api_name: # e.g. dummy-aef + :param api_version: # e.g. v1 + :param result: # e.g. 201 + :param resource_name: # e.g. MONITORING_SUBSCRIPTION_SINGLE + :param protocol: # e.g. HTTP_1_1 or HTTP_2 + :param operation: # e.g. POST + :return: The Log entries found in the CAPIF database + """ + + params = dict() + params.update({'aef-id':self.aef_id}) + + if api_invoker_id is not None: + params.update({'api-invoker-id': api_invoker_id}) + + if time_start is not None: + params.update({'time-range-start': time_start}) + + if time_end is not None: + params.update({'time-range-end': time_end}) + + if api_id is not None: + params.update({'api-id': api_id}) + + if api_name is not None: + params.update({'api-name': api_name}) + + if api_version is not None: + params.update({'api-version': api_version}) + + if result is not None: + params.update({'result': result}) + + if resource_name is not None: + params.update({'resource-name': resource_name}) + + if protocol is not None: + params.update({'protocol': protocol}) + + if operation is not None: + params.update({'operation': operation}) + + response = requests.request("GET", self.capif_query_log_url, params=params, + cert=( + self.certificates_folder + "dummy_amf.crt", + self.certificates_folder + "AMF_private_key.key", + ), + verify=self.certificates_folder + 'ca.crt') + response.raise_for_status() + response_payload = json.loads(response.text) + return response_payload diff --git a/examples/capif_exposer_utils.py b/examples/capif_exposer_utils.py index 0e14045..63c667b 100644 --- a/examples/capif_exposer_utils.py +++ b/examples/capif_exposer_utils.py @@ -6,9 +6,24 @@ def nef_exposer_get_certificate_folder() -> str: def nef_exposer_get_sample_api_description_path() -> str: return "/home/alex/Projects/maggioli/evolved-5g/SDK-CLI/examples/capif_exposer_sample_files/nef_api_description_sample.json" +def nef_exposer_get_sample_api_description_path_that_is_stored_in_capif()->str: + return "/home/alex/Projects/test_nef_certificate_folder/CAPIF_nef_api_description_sample.json" def tsn_exposer_get_certificate_folder() -> str: return "/home/alex/Projects/test_tsn_certificate_folder" def tsn_exposer_get_sample_api_description_path() -> str: return "/home/alex/Projects/maggioli/evolved-5g/SDK-CLI/examples/capif_exposer_sample_files/tsn_api_description_sample.json" + + +def get_demo_invoker_id()->str: + """ + When you register a Net App to CAPIF it is assigner an api invoker id. + You can find api invoker ids in the mongo database of CAPIF. http://localhost:8082/db/capif/invokerdetails + If your CAPIF instance does not have any NetApps registered, you can run example "netapp_capif_connector_examples.py" + + :return: An api_invoker_id that exists in CAPIF database + + """ + + return "33c2f9b99814ddfb7b3e8b671f0d58" diff --git a/examples/emulator_utils.py b/examples/emulator_utils.py index 4177163..39be8c2 100644 --- a/examples/emulator_utils.py +++ b/examples/emulator_utils.py @@ -3,14 +3,6 @@ from evolved5g.swagger_client.models import Token - -def get_api_client_for_nef_emulator(token) -> swagger_client.ApiClient: - configuration = swagger_client.Configuration() - configuration.host = get_url_of_the_nef_emulator() - configuration.access_token = token.access_token - api_client = swagger_client.ApiClient(configuration=configuration) - return api_client - def get_token_for_nef_emulator() -> Token: username = "admin@my-email.com" diff --git a/examples/nef_logger_and_audit_example.py b/examples/nef_logger_and_audit_example.py new file mode 100644 index 0000000..287b610 --- /dev/null +++ b/examples/nef_logger_and_audit_example.py @@ -0,0 +1,86 @@ +from evolved5g.sdk import CAPIFProviderConnector, CAPIFLogger, CAPIFAuditor +import capif_exposer_utils + +def showcase_save_log_to_capif(): + """ + CAPIF Logger class can be used by providers (NEF emulator or TSN) in order to log API request and responses + by invokers. + + This example demonstrates the usage of the CAPIF Logger class. Make sure you have: + + 1.NEF registered and stored to CAPIF. + After registration of NEF to CAPIF make sure that in the certificates folder you can view + a) the generated certificates + b) the capif_provider_details.json + c) a json file for every service that you have published. If during publish, you published a service file with name "nef_api_description_sample.json" + then in this folder you will find a file with name "CAPIF_nef_api_description_sample.json". + This contains the related CAPIF Ids. You need the api id from this file. + + 2. A Net App registered to CAPIF. This Net App should have an associated CAPIF_INVOKER_ID. + You can find api invoker ids in the mongo database of CAPIF. http://localhost:8082/db/capif/invokerdetails + If your CAPIF instance does not have any NetApps registered, you can run example "netapp_capif_connector_examples.py" + """ + + capif_logger = CAPIFLogger(certificates_folder=capif_exposer_utils.nef_exposer_get_certificate_folder(), + capif_host="capifcore", + capif_https_port="443" + ) + log_entries = [] + + service_description = capif_logger.get_capif_service_description(capif_service_api_description_json_full_path= + capif_exposer_utils.nef_exposer_get_sample_api_description_path_that_is_stored_in_capif()) + + api_id = service_description["apiId"] + log_entry = CAPIFLogger.LogEntry(apiId = api_id, + apiVersion="v1", + apiName="/nef/api/v1/3gpp-monitoring-event/", + resourceName="MONITORING_SUBSCRIPTIONS", + uri="/{scsAsId}/subscriptions", + protocol="HTTP_1_1", + invocationTime= "2023-01-24T12:20:00+00:00", + invocationLatency=10, + operation="POST", + result="200", + inputParameters={}, + outputParameters={} + ) + + log_entries.append(log_entry) + # In this example we will save a log entry for a specific api invoker id (for example a Network App) + # When you register a Network App to CAPIF it is assigned an api invoker id. + # You can find api invoker ids in the mongo database of CAPIF. http://localhost:8082/db/capif/invokerdetails + # If your CAPIF instance does not have any Network Apps registered, you can run example "netapp_capif_connector_examples.py" + api_invoker_id = capif_exposer_utils.get_demo_invoker_id() + + capif_logger.save_log(api_invoker_id,log_entries) + +def showcase_quering_the_capif_log(): + capif_auditor = CAPIFAuditor(certificates_folder=capif_exposer_utils.nef_exposer_get_certificate_folder(), + capif_host="capifcore", + capif_https_port="443") + + # In this example we will save a log entry for a specific api invoker id (for example a Network App) + # When you register a Network App to CAPIF it is assigned an api invoker id. + # You can find api invoker ids in the mongo database of CAPIF. http://localhost:8082/db/capif/invokerdetails + # If your CAPIF instance does not have any Network Apps registered, you can run example "netapp_capif_connector_examples.py" + api_invoker_id = capif_exposer_utils.get_demo_invoker_id() + + print("Filtering log for invoker" + api_invoker_id) + query_results_1 = capif_auditor.query_log(api_invoker_id= api_invoker_id) + print(query_results_1) + + # Now let's further query the Log with the API ID parameter. + # API ID is the id of our published service. We can find this parameter in the certificates folder, + # in the relevant .json file that was created during onboarding of the Provider to CAPIF + # The helper method below reads that file: + service_description = capif_auditor.get_capif_service_description(capif_service_api_description_json_full_path= + capif_exposer_utils.nef_exposer_get_sample_api_description_path_that_is_stored_in_capif()) + api_id = service_description["apiId"] + print("Filtering log for invoker" + api_invoker_id + " and api_id " + api_id) + query_results_2 = capif_auditor.query_log(api_invoker_id= api_invoker_id, api_id = api_id) + print(query_results_2) + + +if __name__ == "__main__": + showcase_save_log_to_capif() + showcase_quering_the_capif_log() diff --git a/examples/netapp_capif_connector_examples.py b/examples/netapp_capif_connector_examples.py index d3f6c82..e21f2c4 100644 --- a/examples/netapp_capif_connector_examples.py +++ b/examples/netapp_capif_connector_examples.py @@ -12,7 +12,7 @@ def showcase_capif_connector(): capif_host="capifcore", capif_http_port="8080", capif_https_port="443", - capif_netapp_username="custom_netapp16", + capif_netapp_username="custom_netapp18", capif_netapp_password="pass123", capif_callback_url="http://localhost:5000", description= "Dummy NetApp", diff --git a/setup.cfg b/setup.cfg index 7e1f0fe..6f25d49 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.0.2 +current_version = 1.0.3 commit = True tag = True diff --git a/setup.py b/setup.py index 9d16008..aea3bce 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,6 @@ test_suite="tests", tests_require=test_requirements, url="https://github.com/EVOLVED-5G/SDK-CLI", - version="1.0.2", + version="1.0.3", zip_safe=False, )