#1916: Created FTP superclass + working retrieve file method for FTP
This commit is contained in:
@@ -226,7 +226,7 @@ class FileSystem(SimComponent):
|
|||||||
folder = self.get_folder(folder_name)
|
folder = self.get_folder(folder_name)
|
||||||
if folder:
|
if folder:
|
||||||
return folder.get_file(file_name)
|
return folder.get_file(file_name)
|
||||||
self.fs.sys_log.info(f"file not found /{folder_name}/{file_name}")
|
self.sys_log.info(f"file not found /{folder_name}/{file_name}")
|
||||||
|
|
||||||
def delete_file(self, folder_name: str, file_name: str):
|
def delete_file(self, folder_name: str, file_name: str):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ from primaite.simulator.network.protocols.ftp import FTPCommand, FTPPacket, FTPS
|
|||||||
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
||||||
from primaite.simulator.network.transmission.transport_layer import Port
|
from primaite.simulator.network.transmission.transport_layer import Port
|
||||||
from primaite.simulator.system.core.software_manager import SoftwareManager
|
from primaite.simulator.system.core.software_manager import SoftwareManager
|
||||||
from primaite.simulator.system.services.service import Service
|
from primaite.simulator.system.services.ftp.ftp_service import FTPServiceABC
|
||||||
|
|
||||||
|
|
||||||
class FTPClient(Service):
|
class FTPClient(FTPServiceABC):
|
||||||
"""
|
"""
|
||||||
A class for simulating an FTP client service.
|
A class for simulating an FTP client service.
|
||||||
|
|
||||||
@@ -61,17 +61,6 @@ class FTPClient(Service):
|
|||||||
# return true if connected successfully else false
|
# return true if connected successfully else false
|
||||||
self.connected = False
|
self.connected = False
|
||||||
|
|
||||||
def _process_response(self, payload: FTPPacket):
|
|
||||||
"""
|
|
||||||
Process any FTPPacket responses.
|
|
||||||
|
|
||||||
:param: payload: The FTPPacket payload
|
|
||||||
:type: FTPPacket
|
|
||||||
"""
|
|
||||||
if payload.ftp_command == FTPCommand.PORT:
|
|
||||||
if payload.status_code == FTPStatusCode.OK:
|
|
||||||
self.connected = True
|
|
||||||
|
|
||||||
def send_file(
|
def send_file(
|
||||||
self,
|
self,
|
||||||
dest_ip_address: IPv4Address,
|
dest_ip_address: IPv4Address,
|
||||||
@@ -109,23 +98,13 @@ class FTPClient(Service):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# send STOR request
|
# send STOR request
|
||||||
payload: FTPPacket = FTPPacket(
|
return self._send_data(
|
||||||
ftp_command=FTPCommand.STOR,
|
file=file_to_transfer,
|
||||||
ftp_command_args={
|
dest_folder_name=dest_folder_name,
|
||||||
"dest_folder_name": dest_folder_name,
|
dest_file_name=dest_file_name,
|
||||||
"dest_file_name": dest_file_name,
|
dest_ip_address=dest_ip_address,
|
||||||
"file_size": file_to_transfer.sim_size,
|
dest_port=dest_port,
|
||||||
},
|
|
||||||
packet_payload_size=file_to_transfer.sim_size,
|
|
||||||
)
|
)
|
||||||
software_manager: SoftwareManager = self.software_manager
|
|
||||||
software_manager.send_payload_to_session_manager(
|
|
||||||
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port
|
|
||||||
)
|
|
||||||
|
|
||||||
if payload.status_code == Port.FTP:
|
|
||||||
self._disconnect_from_server()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def request_file(
|
def request_file(
|
||||||
self,
|
self,
|
||||||
@@ -138,7 +117,48 @@ class FTPClient(Service):
|
|||||||
is_reattempt: Optional[bool] = False,
|
is_reattempt: Optional[bool] = False,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Request a file from a target IP address."""
|
"""Request a file from a target IP address."""
|
||||||
pass
|
# check if FTP is currently connected to IP
|
||||||
|
self.connected = self._connect_to_server(
|
||||||
|
dest_ip_address=dest_ip_address,
|
||||||
|
dest_port=dest_port,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.connected:
|
||||||
|
if is_reattempt:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.request_file(
|
||||||
|
src_folder_name=src_folder_name,
|
||||||
|
src_file_name=src_file_name,
|
||||||
|
dest_folder_name=dest_folder_name,
|
||||||
|
dest_file_name=dest_file_name,
|
||||||
|
dest_ip_address=dest_ip_address,
|
||||||
|
dest_port=dest_port,
|
||||||
|
is_reattempt=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# send retrieve request
|
||||||
|
payload: FTPPacket = FTPPacket(
|
||||||
|
ftp_command=FTPCommand.RETR,
|
||||||
|
ftp_command_args={
|
||||||
|
"src_folder_name": src_folder_name,
|
||||||
|
"src_file_name": src_file_name,
|
||||||
|
"dest_file_name": dest_file_name,
|
||||||
|
"dest_folder_name": dest_folder_name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
software_manager: SoftwareManager = self.software_manager
|
||||||
|
software_manager.send_payload_to_session_manager(
|
||||||
|
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port
|
||||||
|
)
|
||||||
|
|
||||||
|
# the payload should have ok status code
|
||||||
|
if payload.status_code == FTPStatusCode.OK:
|
||||||
|
self.sys_log.info(f"File {src_folder_name}/{src_file_name} found in FTP server.")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.sys_log.error(f"File {src_folder_name}/{src_file_name} does not exist in FTP server")
|
||||||
|
return False
|
||||||
|
|
||||||
def receive(self, payload: FTPPacket, session_id: Optional[str] = None, **kwargs) -> bool:
|
def receive(self, payload: FTPPacket, session_id: Optional[str] = None, **kwargs) -> bool:
|
||||||
"""Receives a payload from the SessionManager."""
|
"""Receives a payload from the SessionManager."""
|
||||||
@@ -146,5 +166,5 @@ class FTPClient(Service):
|
|||||||
self.sys_log.error(f"{payload} is not an FTP packet")
|
self.sys_log.error(f"{payload} is not an FTP packet")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._process_response(payload=payload)
|
self._process_ftp_command(payload=payload, session_id=session_id)
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from primaite.simulator.network.protocols.ftp import FTPCommand, FTPPacket, FTPStatusCode
|
from primaite.simulator.network.protocols.ftp import FTPPacket
|
||||||
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
||||||
from primaite.simulator.network.transmission.transport_layer import Port
|
from primaite.simulator.network.transmission.transport_layer import Port
|
||||||
from primaite.simulator.system.services.service import Service
|
from primaite.simulator.system.services.ftp.ftp_service import FTPServiceABC
|
||||||
|
|
||||||
|
|
||||||
class FTPServer(Service):
|
class FTPServer(FTPServiceABC):
|
||||||
"""
|
"""
|
||||||
A class for simulating an FTP server service.
|
A class for simulating an FTP server service.
|
||||||
|
|
||||||
@@ -24,48 +24,11 @@ class FTPServer(Service):
|
|||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def _process_ftp_command(self, payload: FTPPacket) -> FTPPacket:
|
|
||||||
# handle PORT request
|
|
||||||
if payload.ftp_command == FTPCommand.PORT:
|
|
||||||
# check that the port is valid
|
|
||||||
if isinstance(payload.ftp_command_args, Port) and payload.ftp_command_args.value in range(0, 65535):
|
|
||||||
# return successful connection
|
|
||||||
payload.status_code = FTPStatusCode.OK
|
|
||||||
|
|
||||||
# handle STOR request
|
|
||||||
if payload.ftp_command == FTPCommand.STOR:
|
|
||||||
# check that the file is created in the computed hosting the FTP server
|
|
||||||
if self._process_store_data(payload=payload):
|
|
||||||
payload.status_code = FTPStatusCode.OK
|
|
||||||
|
|
||||||
return payload
|
|
||||||
|
|
||||||
def _process_store_data(self, payload: FTPPacket) -> bool:
|
|
||||||
"""Handle the transfer of data from Client to this Server."""
|
|
||||||
try:
|
|
||||||
file_name = payload.ftp_command_args["dest_file_name"]
|
|
||||||
folder_name = payload.ftp_command_args["dest_folder_name"]
|
|
||||||
file_size = payload.ftp_command_args["file_size"]
|
|
||||||
self.file_system.create_file(
|
|
||||||
file_name=file_name,
|
|
||||||
folder_name=folder_name,
|
|
||||||
size=file_size,
|
|
||||||
)
|
|
||||||
self.sys_log.info(
|
|
||||||
f"Created item in {self.name}: {payload.ftp_command_args['dest_folder_name']}/"
|
|
||||||
f"{payload.ftp_command_args['dest_file_name']}"
|
|
||||||
)
|
|
||||||
# file should exist
|
|
||||||
return self.file_system.get_file(file_name=file_name, folder_name=folder_name) is not None
|
|
||||||
except Exception as e:
|
|
||||||
self.sys_log.error(f"Unable to store file in {self.name}: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def receive(self, payload: Any, session_id: Optional[str] = None, **kwargs) -> bool:
|
def receive(self, payload: Any, session_id: Optional[str] = None, **kwargs) -> bool:
|
||||||
"""Receives a payload from the SessionManager."""
|
"""Receives a payload from the SessionManager."""
|
||||||
if not isinstance(payload, FTPPacket):
|
if not isinstance(payload, FTPPacket):
|
||||||
self.sys_log.error(f"{payload} is not an FTP packet")
|
self.sys_log.error(f"{payload} is not an FTP packet")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.send(self._process_ftp_command(payload=payload), session_id)
|
self.send(self._process_ftp_command(payload=payload, session_id=session_id), session_id)
|
||||||
return True
|
return True
|
||||||
|
|||||||
132
src/primaite/simulator/system/services/ftp/ftp_service.py
Normal file
132
src/primaite/simulator/system/services/ftp/ftp_service.py
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
from abc import ABC
|
||||||
|
from ipaddress import IPv4Address
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from primaite.simulator.file_system.file_system import File
|
||||||
|
from primaite.simulator.network.protocols.ftp import FTPCommand, FTPPacket, FTPStatusCode
|
||||||
|
from primaite.simulator.network.transmission.transport_layer import Port
|
||||||
|
from primaite.simulator.system.core.software_manager import SoftwareManager
|
||||||
|
from primaite.simulator.system.services.service import Service
|
||||||
|
|
||||||
|
|
||||||
|
class FTPServiceABC(Service, ABC):
|
||||||
|
"""
|
||||||
|
Abstract Base Class for FTP Client and Service.
|
||||||
|
|
||||||
|
Contains shared methods between both classes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _process_ftp_command(self, payload: FTPPacket, session_id: Optional[str] = None, **kwargs) -> FTPPacket:
|
||||||
|
# handle PORT request
|
||||||
|
if payload.ftp_command == FTPCommand.PORT:
|
||||||
|
# check that the port is valid
|
||||||
|
if isinstance(payload.ftp_command_args, Port) and payload.ftp_command_args.value in range(0, 65535):
|
||||||
|
# return successful connection
|
||||||
|
payload.status_code = FTPStatusCode.OK
|
||||||
|
|
||||||
|
# handle STOR request
|
||||||
|
if payload.ftp_command == FTPCommand.STOR:
|
||||||
|
# check that the file is created in the computed hosting the FTP server
|
||||||
|
if self._store_data(payload=payload):
|
||||||
|
payload.status_code = FTPStatusCode.OK
|
||||||
|
|
||||||
|
if payload.ftp_command == FTPCommand.RETR:
|
||||||
|
# check that the file exists in the FTP Server
|
||||||
|
file: File = self.file_system.get_file(
|
||||||
|
folder_name=payload.ftp_command_args["src_folder_name"],
|
||||||
|
file_name=payload.ftp_command_args["src_file_name"],
|
||||||
|
)
|
||||||
|
if file:
|
||||||
|
payload.status_code = FTPStatusCode.OK
|
||||||
|
self._send_data(
|
||||||
|
file=file,
|
||||||
|
dest_folder_name=payload.ftp_command_args["dest_folder_name"],
|
||||||
|
dest_file_name=payload.ftp_command_args["dest_file_name"],
|
||||||
|
session_id=session_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
return payload
|
||||||
|
|
||||||
|
def _store_data(self, payload: FTPPacket) -> bool:
|
||||||
|
"""
|
||||||
|
Handle the transfer of data.
|
||||||
|
|
||||||
|
:param: payload: The FTP Packet that contains the file data
|
||||||
|
:type: FTPPacket
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
file_name = payload.ftp_command_args["dest_file_name"]
|
||||||
|
folder_name = payload.ftp_command_args["dest_folder_name"]
|
||||||
|
file_size = payload.ftp_command_args["file_size"]
|
||||||
|
self.file_system.create_file(
|
||||||
|
file_name=file_name,
|
||||||
|
folder_name=folder_name,
|
||||||
|
size=file_size,
|
||||||
|
)
|
||||||
|
self.sys_log.info(
|
||||||
|
f"Created item in {self.sys_log.hostname}: {payload.ftp_command_args['dest_folder_name']}/"
|
||||||
|
f"{payload.ftp_command_args['dest_file_name']}"
|
||||||
|
)
|
||||||
|
# file should exist
|
||||||
|
return self.file_system.get_file(file_name=file_name, folder_name=folder_name) is not None
|
||||||
|
except Exception as e:
|
||||||
|
self.sys_log.error(f"Unable to create file in {self.sys_log.hostname}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _send_data(
|
||||||
|
self,
|
||||||
|
file: File,
|
||||||
|
dest_folder_name: str,
|
||||||
|
dest_file_name: str,
|
||||||
|
dest_ip_address: Optional[IPv4Address] = None,
|
||||||
|
dest_port: Optional[Port] = None,
|
||||||
|
session_id: Optional[str] = None,
|
||||||
|
) -> bool:
|
||||||
|
# send STOR request
|
||||||
|
payload: FTPPacket = FTPPacket(
|
||||||
|
ftp_command=FTPCommand.STOR,
|
||||||
|
ftp_command_args={
|
||||||
|
"dest_folder_name": dest_folder_name,
|
||||||
|
"dest_file_name": dest_file_name,
|
||||||
|
"file_size": file.sim_size,
|
||||||
|
},
|
||||||
|
packet_payload_size=file.sim_size,
|
||||||
|
)
|
||||||
|
software_manager: SoftwareManager = self.software_manager
|
||||||
|
software_manager.send_payload_to_session_manager(
|
||||||
|
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if payload.status_code == FTPStatusCode.OK:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _retrieve_data(self, payload: FTPPacket, session_id: Optional[str] = None) -> bool:
|
||||||
|
"""
|
||||||
|
Handle the transfer of data from Server to Client.
|
||||||
|
|
||||||
|
:param: payload: The FTP Packet that contains the file data
|
||||||
|
:type: FTPPacket
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# find the file
|
||||||
|
file_name = payload.ftp_command_args["src_file_name"]
|
||||||
|
folder_name = payload.ftp_command_args["src_folder_name"]
|
||||||
|
retrieved_file: File = self.file_system.get_file(folder_name=folder_name, file_name=file_name)
|
||||||
|
|
||||||
|
# if file does not exist, return an error
|
||||||
|
if not retrieved_file:
|
||||||
|
self.sys_log.error(
|
||||||
|
f"File {payload.ftp_command_args['dest_folder_name']}/"
|
||||||
|
f"{payload.ftp_command_args['dest_file_name']} does not exist in {self.sys_log.hostname}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# send requested data
|
||||||
|
return self._send_data(
|
||||||
|
file=retrieved_file, dest_file_name=file_name, dest_folder_name=folder_name, session_id=session_id
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self.sys_log.error(f"Unable to retrieve file from {self.sys_log.hostname}: {e}")
|
||||||
|
return False
|
||||||
@@ -57,3 +57,6 @@ def test_ftp_client_retrieve_file_from_server(uc2_network):
|
|||||||
dest_file_name="test_file.txt",
|
dest_file_name="test_file.txt",
|
||||||
dest_ip_address=backup_server.nics.get(next(iter(backup_server.nics))).ip_address,
|
dest_ip_address=backup_server.nics.get(next(iter(backup_server.nics))).ip_address,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# client should have retrieved the file
|
||||||
|
assert ftp_client.file_system.get_file(folder_name="downloads", file_name="test_file.txt")
|
||||||
|
|||||||
Reference in New Issue
Block a user