diff --git a/Dockerfile.api b/Dockerfile.api index 1082e7f..2f68316 100644 --- a/Dockerfile.api +++ b/Dockerfile.api @@ -1,7 +1,7 @@ FROM --platform=linux/amd64 python:3.9-slim RUN apt-get update && \ - apt-get install iperf3 hping3 iputils-ping net-tools -y + apt-get install iperf3 hping3 iputils-ping net-tools nmap -y COPY ./src /app/src COPY requirements.txt /app diff --git a/README.md b/README.md index 7da4352..796e932 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,57 @@ ## How to run -1. Requirements -- docker -- docker-compose +1. To run the miniAPI and make it fully operational, we first need to install some OS-level dependencies: -2. Run ```bash -docker-compose up +sudo apt update + +# Install dependencies - iperf3, ss, ping, netstat, and nmap +sudo apt install iperf3 iproute2 iputils-ping net-tools nmap -y +``` + +2. Then, clone the repository: + +```bash +cd ~ +git clone https://github.com/5gasp/NetworkAppControl-MiniAPI.git +cd ~/NetworkAppControl-MiniAPI/ ``` + +3. Now, you need to install the miniAPI requirements. This can be done on the host or using a virtual environment. +The following commands may be used to install the miniAPI requirements in an Ubuntu 22.04. If you use a different OS, you may need to follow an alternative approach. + +* On host: +```bash +sudo apt install python3-pip -y +pip3 install --upgrade pip +pip3 install -r requirements.txt +``` + +* Using venv: + +```bash +sudo apt install python3.10-venv -y # Depending on your Ubuntu version, you may have to update this command +cd ~/NetworkAppControl-MiniAPI/ +python3 -m venv venv +source venv/bin/activate +pip install --upgrade pip +pip install -r requirements.txt +``` + +4. Finally, run the miniAPI: + +```bash +cd ~/NetworkAppControl-MiniAPI/src +python3 -m uvicorn main:app --host=0.0.0.0 --port=3001 +``` + +### Run using Docker + +1. Docker must be installed on the OS. You can find the official tutorial on how to install Docker [here](https://docs.docker.com/desktop/install/linux-install/). + +2. Run: +```bash +cd ~/NetworkAppControl-MiniAPI/ +docker compose up +``` \ No newline at end of file diff --git a/debian/changelog b/debian/changelog index f74a926..f315665 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,16 @@ +miniapi (0.0.4) unstable; urgency=low + + * Integration of upstream version ff41b2e (Thu Feb 15 16:40:27 2024) + + -- Christophe COUTURIER Thu, 15 Feb 2024 20:54:00 +0100 + +miniapi (0.0.3) unstable; urgency=low + + * Integration of upstream version 7465ff7 (Mon Feb 5 09:00:04 2024) + * Added dependencies: iperf3, iproute2, iputils-ping, net-tools, nmap + + -- Christophe COUTURIER Wed, 14 Feb 2024 14:58:09 +0100 + miniapi (0.0.2) unstable; urgency=low * Integration of upstream version c775644 (Sat Jan 6 12:05:14 2024) diff --git a/debian/control b/debian/control index d5cc0b6..9df163c 100644 --- a/debian/control +++ b/debian/control @@ -8,6 +8,6 @@ Standards-Version: 3.9.5 Package: miniapi Architecture: any Pre-Depends: dpkg (>= 1.16.1), python3-minimal, ${misc:Pre-Depends} -Depends: ${python3:Depends}, ${misc:Depends}, python3-pip +Depends: ${python3:Depends}, ${misc:Depends}, python3-pip, iperf3, iproute2, iputils-ping, net-tools, nmap Description: API to trigger the NEF tests of the 5GASP Project API to trigger the NEF tests of the 5GASP Project diff --git a/docker-compose.yml b/docker-compose.yml index 511276c..c37221c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,4 +9,5 @@ services: context: . dockerfile: Dockerfile.api ports: - - "3001:3001" \ No newline at end of file + - "3001:3001" + - "5201:5201" \ No newline at end of file diff --git a/src/aux/operations_ids.py b/src/aux/operations_ids.py index daa8fb4..1c1d62b 100644 --- a/src/aux/operations_ids.py +++ b/src/aux/operations_ids.py @@ -8,19 +8,21 @@ class OPERATION(Enum): ### NEF - LOGIN = '1' + NEF_AUTHENTICATION = 'Def19Sec9' + AUTHENTICATION_WITH_5GS = 'Def115G1' CREATE_UE = '2' GET_UES = '3' - SUBSCRIPTION = '4' - UE_PATH_LOSS = '5' - SERVING_CELL_INFO = '6' - HANDOVER = '9' - SUBSCRIBE_QOS_EVENT = "def115G7" + NEF_LOCATION_SUBSCRIPTION = 'Def115G2' + UE_PATH_LOSS = 'Def115G5' + SERVING_CELL_INFO = 'Def115G6' + HANDOVER = 'Def115G3' + SUBSCRIBE_QOS_EVENT = "Def115G7" E2E_SINGLE_UE_LATENCY_AND_THROUGHPUT = "Def14Perf1" E2E_MULTIPLE_UE_LATENCY_AND_THROUGHPUT = "Def14Perf2" + ACQUISITION_OF_RSRP = "Def115G4" ### AVAILABILITY - E2E_UE_PERFORMANCE = '7' - E2E_UE_RTT_PERFORMANCE = '8' + # E2E_UE_PERFORMANCE = '7' + # E2E_UE_RTT_PERFORMANCE = '8' MAX_HOPS = "Def14Perf13" MAX_CONNECTIONS = "Def14Perf11" NEF_CALLBACK_MAX_CONNECTIONS = "Def14Perf7" \ No newline at end of file diff --git a/src/aux/ping_wrapper.py b/src/aux/ping_wrapper.py index c3a7671..aadadc0 100644 --- a/src/aux/ping_wrapper.py +++ b/src/aux/ping_wrapper.py @@ -21,7 +21,7 @@ def run(self): self.transmitter.count = PING_TIMEOUT res = self.transmitter.ping() res = self.parser.parse(res).as_dict() - with open(f'./static/{variables.E2E_RTT_RESULTS}', 'w') as json_file: + with open(f'/tmp/{variables.E2E_RTT_RESULTS}', 'w') as json_file: json.dump(res, json_file) @@ -39,7 +39,7 @@ def run_hping(self): # Parse the output parsed_output = parse_hping_output(output) - with open(f'./static/{variables.E2E_RTT_RESULTS}', 'w') as json_file: + with open(f'/tmp/{variables.E2E_RTT_RESULTS}', 'w') as json_file: json.dump(parsed_output, json_file) except subprocess.CalledProcessError as e: print(f"Error running hping3: {e}") diff --git a/src/miniapi/main.py b/src/miniapi/main.py index 0af9c8f..c5d77c3 100644 --- a/src/miniapi/main.py +++ b/src/miniapi/main.py @@ -19,13 +19,10 @@ from aux.operations_ids import OPERATION from nef_operations import operations as nef_operations from performance_operations import operations as perf_operations -from fastapi.staticfiles import StaticFiles models.Base.metadata.create_all(bind=engine) app = FastAPI() -#app.mount("/static", StaticFiles(directory="static"), name="static") -app.mount("/static", StaticFiles(directory="/tmp"), name="static") RUNNING_PROCESSES = { OPERATION.MAX_CONNECTIONS.value: [], @@ -89,7 +86,8 @@ async def start_test( ue_count: int = None, target: str = None): try: - if operation_id == OPERATION.LOGIN.value: + if operation_id == OPERATION.NEF_AUTHENTICATION.value or\ + operation_id == OPERATION.AUTHENTICATION_WITH_5GS: token = nef_operations.login( ip=variables.VARIABLES["NEF_IP"], port=variables.VARIABLES["NEF_PORT"], @@ -121,13 +119,16 @@ async def start_test( ) return JSONResponse(content="Got UEs", status_code=200) - if operation_id == OPERATION.SUBSCRIPTION.value: + if operation_id == OPERATION.NEF_LOCATION_SUBSCRIPTION.value: nef_operations.subscribe_event( ip=variables.VARIABLES["NEF_IP"], port=variables.VARIABLES["NEF_PORT"], - callback_url=variables.VARIABLES["SUBS1_CALLBACK_URL"], - monitoring_type=variables.VARIABLES["SUBS1_MONITORING_TYPE"], - monitoring_expire_time=variables.VARIABLES["SUBS1_MONITORING_EXPIRE_TIME"], + callback_url=variables.VARIABLES["SUBS_CALLBACK_URL"], + monitoring_type=variables.VARIABLES["SUBS_MONITORING_TYPE"], + monitoring_expire_time=variables.VARIABLES[ + "SUBS_MONITORING_EXPIRE_TIME" + ], + external_id=variables.VARIABLES["SUBS_EXTERNAL_ID"], token = variables.VARIABLES["AUTH_TOKEN"] ) return JSONResponse(content="Subscription Done", status_code=200) @@ -135,18 +136,30 @@ async def start_test( nef_operations.get_ue_path_loss( ip=variables.VARIABLES["NEF_IP"], port=variables.VARIABLES["NEF_PORT"], - ue_supi=variables.VARIABLES["UE1_SUPI"], + + ue_supi=variables.VARIABLES["UE1_SUPI"], + token = variables.VARIABLES["AUTH_TOKEN"] + ) + return JSONResponse(content="Got UE Path Loss Information", status_code=200) + + if operation_id == OPERATION.ACQUISITION_OF_RSRP.value: + nef_operations.get_ue_path_loss( + ip=variables.VARIABLES["NEF_IP"], + port=variables.VARIABLES["NEF_PORT"], + ue_supi=variables.VARIABLES["UE1_SUPI"], token = variables.VARIABLES["AUTH_TOKEN"] ) return JSONResponse(content="Got UE Path Loss Information", status_code=200) + if operation_id == OPERATION.SERVING_CELL_INFO.value: - nef_operations.get_serving_cell_info( + nef_operations.get_rsrp_info( ip=variables.VARIABLES["NEF_IP"], port=variables.VARIABLES["NEF_PORT"], ue_supi=variables.VARIABLES["UE1_SUPI"], token = variables.VARIABLES["AUTH_TOKEN"] ) - return JSONResponse(content="Got UE Serving Cell Information", status_code=200) + return JSONResponse(content="Got UE RSRP Information", status_code=200) + if operation_id == OPERATION.HANDOVER.value: nef_operations.get_ue_handover_event( @@ -160,10 +173,10 @@ async def start_test( if operation_id == OPERATION.E2E_SINGLE_UE_LATENCY_AND_THROUGHPUT.value: # Delete old results file if os.path.exists( - f'./static/{variables.E2E_SINGLE_UE_THROUGHPUT_AND_LATENCY}' + f'/tmp/{variables.E2E_SINGLE_UE_THROUGHPUT_AND_LATENCY}' ): os.remove( - f'./static/{variables.E2E_SINGLE_UE_THROUGHPUT_AND_LATENCY}' + f'/tmp/{variables.E2E_SINGLE_UE_THROUGHPUT_AND_LATENCY}' ) error_message = None @@ -208,10 +221,10 @@ async def start_test( if operation_id == OPERATION.E2E_MULTIPLE_UE_LATENCY_AND_THROUGHPUT.value: # Delete old results file if os.path.exists( - f'./static/{variables.E2E_SINGLE_UE_THROUGHPUT_AND_LATENCY}' + f'/tmp/{variables.E2E_SINGLE_UE_THROUGHPUT_AND_LATENCY}' ): os.remove( - f'./static/{variables.E2E_SINGLE_UE_THROUGHPUT_AND_LATENCY}' + f'/tmp/{variables.E2E_SINGLE_UE_THROUGHPUT_AND_LATENCY}' ) error_message = None @@ -262,8 +275,8 @@ async def start_test( if operation_id == OPERATION.MAX_HOPS.value: # Delete old results file - if os.path.exists(f'./static/{variables.MAX_HOPS_RESULTS}'): - os.remove(f'./static/{variables.MAX_HOPS_RESULTS}') + if os.path.exists(f'/tmp/{variables.MAX_HOPS_RESULTS}'): + os.remove(f'/tmp/{variables.MAX_HOPS_RESULTS}') # Start the number of hops until target process max_hops_process = perf_operations.start_max_hops_computing( @@ -281,11 +294,11 @@ async def start_test( if operation_id == OPERATION.MAX_CONNECTIONS.value: # Delete old results file - if os.path.exists(f'./static/{variables.MAX_CONNECTIONS_RESULTS}'): - os.remove(f'./static/{variables.MAX_CONNECTIONS_RESULTS}') + if os.path.exists(f'/tmp/{variables.MAX_CONNECTIONS_RESULTS}'): + os.remove(f'/tmp/{variables.MAX_CONNECTIONS_RESULTS}') # Start the netstat loop netstat_process = perf_operations.start_netstat_command( - output_file=f'./static/{variables.MAX_CONNECTIONS_RESULTS}' + output_file=f'/tmp/{variables.MAX_CONNECTIONS_RESULTS}' ) # If we can start a monitoring process everything is ok @@ -312,7 +325,7 @@ async def start_test( nef_operations.subscribe_qos_event( ip=variables.VARIABLES["NEF_IP"], port=variables.VARIABLES["NEF_PORT"], - callback_url=variables.VARIABLES["SUBS1_CALLBACK_URL"], + callback_url=variables.VARIABLES["SUBS_CALLBACK_URL"], token = variables.VARIABLES["AUTH_TOKEN"], monitoring_payload = monitoring_payload ) @@ -322,15 +335,15 @@ async def start_test( if operation_id == OPERATION.NEF_CALLBACK_MAX_CONNECTIONS.value: # Delete old results file if os.path.exists( - f'./static/{variables.NEF_CALLBACK_MAX_CONNECTIONS_RESULTS}' + f'/tmp/{variables.NEF_CALLBACK_MAX_CONNECTIONS_RESULTS}' ): os.remove( - f'./static/{variables.NEF_CALLBACK_MAX_CONNECTIONS_RESULTS}' + f'/tmp/{variables.NEF_CALLBACK_MAX_CONNECTIONS_RESULTS}' ) # Start the netstat loop netstat_process = perf_operations.start_netstat_command( - output_file=f'./static/{variables.NEF_CALLBACK_MAX_CONNECTIONS_RESULTS}' + output_file=f'/tmp/{variables.NEF_CALLBACK_MAX_CONNECTIONS_RESULTS}' ) # If we can start a monitoring process everything is ok @@ -372,12 +385,12 @@ async def get_report(operation_id: str): if operation_id == OPERATION.MAX_CONNECTIONS.value: return FileResponse( - path=f'./static/{variables.MAX_CONNECTIONS_RESULTS}' + path=f'/tmp/{variables.MAX_CONNECTIONS_RESULTS}' ) if operation_id == OPERATION.NEF_CALLBACK_MAX_CONNECTIONS.value: return FileResponse( - path=f'./static/{variables.NEF_CALLBACK_MAX_CONNECTIONS_RESULTS}' + path=f'/tmp/{variables.NEF_CALLBACK_MAX_CONNECTIONS_RESULTS}' ) if operation_id in [ @@ -387,7 +400,7 @@ async def get_report(operation_id: str): # The test may still be running when the user requests its results try: with open( - f'./static/{variables.E2E_SINGLE_UE_THROUGHPUT_AND_LATENCY}', + f'/tmp/{variables.E2E_SINGLE_UE_THROUGHPUT_AND_LATENCY}', "r" ) as file: data = json.load(file) @@ -412,13 +425,13 @@ async def get_report(operation_id: str): if operation_id == OPERATION.MAX_HOPS.value: # The test may still be running when the user requests its results - if not os.path.exists(f'./static/{variables.MAX_HOPS_RESULTS}'): + if not os.path.exists(f'/tmp/{variables.MAX_HOPS_RESULTS}'): return JSONResponse( content=f"The Max Hops Performance Test is not finished yet!", status_code=404 ) - with open(f'./static/{variables.MAX_HOPS_RESULTS}', "r") as file: + with open(f'/tmp/{variables.MAX_HOPS_RESULTS}', "r") as file: data = json.load(file) return JSONResponse( content=data, diff --git a/src/nef_operations/operations.py b/src/nef_operations/operations.py index 239842e..095304a 100644 --- a/src/nef_operations/operations.py +++ b/src/nef_operations/operations.py @@ -103,9 +103,8 @@ def get_ues(ip, port, token): response.raise_for_status() -def subscribe_event( - ip, port, callback_url, monitoring_type, monitoring_expire_time, token -): +def subscribe_event (ip, port, callback_url, monitoring_type, + monitoring_expire_time, external_id, token): url = f"http://{ip}:{port}/nef/api/v1/3gpp-monitoring-event/" \ "v1/netapp/subscriptions" @@ -116,7 +115,7 @@ def subscribe_event( headers["Content-Type"] = "application/json" monitoring_payload = { - "externalId": "123456789@domain.com", + "externalId": external_id, "notificationDestination": callback_url, "monitoringType": monitoring_type, "maximumNumberOfReports": 1, @@ -171,7 +170,7 @@ def create_ue(ip, port, ue_name, ue_description, def get_ue_path_loss(ip, port, ue_supi, token): print("starting....") - url = f"http://{ip}:{port}/api/v1/UEs/{ue_supi}/path_losses" + url = f"http://{ip}:{port}/test/api/v1/UEs/{ue_supi}/path_losses" headers = {} headers["accept"] = "application/json" @@ -193,7 +192,33 @@ def get_serving_cell_info(ip, port, ue_supi, token): ip, port, ue_supi, token ) - url = f"http://{ip}:{port}/api/v1/UEs/{ue_supi}/serving_cell" + url = f"http://{ip}:{port}/test/api/v1/UEs/{ue_supi}/serving_cell" + + headers = {} + headers["accept"] = "application/json" + headers["Authorization"] = "Bearer " + token + headers["Content-Type"] = "application/json" + + response = requests.get( + url=url, + headers=headers, + ) + + print("Get UE Serving Cell Information", response.text) + if response.status_code not in [200, 201, 409]: + response.raise_for_status() + + stop_ue_movement_loop( + ip, port, ue_supi, token + ) + +def get_rsrp_info(ip, port, ue_supi, token): + # To get the Serving Cell Info, we required to start a new UE Movement Loop + create_ue_movement_loop( + ip, port, ue_supi, token + ) + + url = f"http://{ip}:{port}/test/api/v1/UEs/{ue_supi}/rsrps" headers = {} headers["accept"] = "application/json" @@ -235,7 +260,7 @@ def subscribe_qos_event (ip, port, callback_url, token, monitoring_payload): url = f"http://{ip}:{port}/nef/api/v1/3gpp-as-session-with-qos/" \ "v1/netapp/subscriptions" - + headers = {} headers["accept"] = "application/json" headers["Authorization"] = "Bearer " + token diff --git a/src/performance_operations/operations.py b/src/performance_operations/operations.py index c3d10a5..f2b785c 100644 --- a/src/performance_operations/operations.py +++ b/src/performance_operations/operations.py @@ -14,7 +14,7 @@ def create_process_group(): def start_iperf_client(target_ip, number_of_streams): command = f"iperf3 -t 5 -c {target_ip} -P {number_of_streams} -J > "\ - f"./static/{variables.E2E_SINGLE_UE_THROUGHPUT_AND_LATENCY}" + f"/tmp/{variables.E2E_SINGLE_UE_THROUGHPUT_AND_LATENCY}" # Run the command as a background process process = subprocess.Popen( @@ -64,7 +64,7 @@ def start_ping(target_ip, runs): for run in range(runs): command = f"ping -c 5 {target_ip} | grep time= | "\ "awk '{print $7}' | cut -d'=' -f2 > "\ - f"./static/{variables.E2E_SINGLE_UE_LATENCY_BASE_NAME}_{i}.json" + f"/tmp/{variables.E2E_SINGLE_UE_LATENCY_BASE_NAME}_{i}.json" # Run the command as a background process process = subprocess.Popen( @@ -84,7 +84,7 @@ def compute_max_hops(target): if response == 0: print(f"Reached {target} with {ttl} hops!") with open( - f'./static/{variables.MAX_HOPS_RESULTS}', + f'/tmp/{variables.MAX_HOPS_RESULTS}', 'w' ) as json_file: json.dump( @@ -103,7 +103,7 @@ def compute_max_hops(target): # Finally, create an output file for the unsuccessful test cases with open( - f'./static/{variables.MAX_HOPS_RESULTS}', + f'/tmp/{variables.MAX_HOPS_RESULTS}', 'w' ) as json_file: json.dump(