#1796: Add ability to create and restore database backups + add more sys log messages + remove the link size checks temporarily
This commit is contained in:
@@ -498,7 +498,9 @@ class Link(SimComponent):
|
|||||||
def _can_transmit(self, frame: Frame) -> bool:
|
def _can_transmit(self, frame: Frame) -> bool:
|
||||||
if self.is_up:
|
if self.is_up:
|
||||||
frame_size_Mbits = frame.size_Mbits # noqa - Leaving it as Mbits as this is how they're expressed
|
frame_size_Mbits = frame.size_Mbits # noqa - Leaving it as Mbits as this is how they're expressed
|
||||||
return self.current_load + frame_size_Mbits <= self.bandwidth
|
# return self.current_load + frame_size_Mbits <= self.bandwidth
|
||||||
|
# TODO: re add this check once packet size limiting and MTU checks are implemented
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def transmit_frame(self, sender_nic: Union[NIC, SwitchPort], frame: Frame) -> bool:
|
def transmit_frame(self, sender_nic: Union[NIC, SwitchPort], frame: Frame) -> bool:
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ def arcd_uc2_network() -> Network:
|
|||||||
database_server.software_manager.install(DatabaseService)
|
database_server.software_manager.install(DatabaseService)
|
||||||
database_service: DatabaseService = database_server.software_manager.software["DatabaseService"] # noqa
|
database_service: DatabaseService = database_server.software_manager.software["DatabaseService"] # noqa
|
||||||
database_service.start()
|
database_service.start()
|
||||||
|
database_service.configure_backup(backup_server=IPv4Address("192.168.1.16"))
|
||||||
database_service._process_sql(ddl, None) # noqa
|
database_service._process_sql(ddl, None) # noqa
|
||||||
for insert_statement in user_insert_statements:
|
for insert_statement in user_insert_statements:
|
||||||
database_service._process_sql(insert_statement, None) # noqa
|
database_service._process_sql(insert_statement, None) # noqa
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from ipaddress import IPv4Address
|
||||||
from sqlite3 import OperationalError
|
from sqlite3 import OperationalError
|
||||||
from typing import Any, Dict, List, Optional, Union
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
@@ -9,6 +10,7 @@ from primaite.simulator.file_system.file_system import File
|
|||||||
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.ftp.ftp_client import FTPClient
|
||||||
from primaite.simulator.system.services.service import Service, ServiceOperatingState
|
from primaite.simulator.system.services.service import Service, ServiceOperatingState
|
||||||
from primaite.simulator.system.software import SoftwareHealthState
|
from primaite.simulator.system.software import SoftwareHealthState
|
||||||
|
|
||||||
@@ -23,6 +25,15 @@ class DatabaseService(Service):
|
|||||||
password: Optional[str] = None
|
password: Optional[str] = None
|
||||||
connections: Dict[str, datetime] = {}
|
connections: Dict[str, datetime] = {}
|
||||||
|
|
||||||
|
backup_server: IPv4Address = None
|
||||||
|
"""IP address of the backup server."""
|
||||||
|
|
||||||
|
latest_backup_directory: str = None
|
||||||
|
"""Directory of latest backup."""
|
||||||
|
|
||||||
|
latest_backup_file_name: str = None
|
||||||
|
"""File name of latest backup."""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
kwargs["name"] = "DatabaseService"
|
kwargs["name"] = "DatabaseService"
|
||||||
kwargs["port"] = Port.POSTGRES_SERVER
|
kwargs["port"] = Port.POSTGRES_SERVER
|
||||||
@@ -58,6 +69,96 @@ class DatabaseService(Service):
|
|||||||
table.add_row([row])
|
table.add_row([row])
|
||||||
print(table)
|
print(table)
|
||||||
|
|
||||||
|
def configure_backup(self, backup_server: IPv4Address):
|
||||||
|
"""
|
||||||
|
Set up the database backup.
|
||||||
|
|
||||||
|
:param: backup_server_ip: The IP address of the backup server
|
||||||
|
"""
|
||||||
|
self.backup_server = backup_server
|
||||||
|
|
||||||
|
def backup_database(
|
||||||
|
self, backup_directory: Optional[str] = "db_backup", backup_file_name: Optional[str] = None
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Create a backup of the database to the configured backup server.
|
||||||
|
|
||||||
|
:param: backup_directory: Name of directory where backup will be stored. Optional.
|
||||||
|
:type: backup_directory: Optional[str]
|
||||||
|
|
||||||
|
:param: backup_file_name: Name of file where backup will be stored. Optional.
|
||||||
|
:type: backup_file_name: Optional[str]
|
||||||
|
"""
|
||||||
|
# check if the backup server was configured
|
||||||
|
if self.backup_server is None:
|
||||||
|
self.sys_log.error(f"{self.name} - {self.sys_log.hostname}: not configured.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if backup_file_name is None:
|
||||||
|
backup_file_name = f"{datetime.now().strftime('%d-%m-%Y_%H-%M')}.db"
|
||||||
|
|
||||||
|
software_manager: SoftwareManager = self.software_manager
|
||||||
|
ftp_client_service: FTPClient = software_manager.software["FTPClient"]
|
||||||
|
|
||||||
|
# send backup copy of database file to FTP server
|
||||||
|
response = ftp_client_service.send_file(
|
||||||
|
dest_ip_address=self.backup_server,
|
||||||
|
src_file_name=self._db_file.name,
|
||||||
|
src_folder_name=self._db_file.folder.name,
|
||||||
|
dest_folder_name=backup_directory,
|
||||||
|
dest_file_name=backup_file_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
if response:
|
||||||
|
self.latest_backup_directory = backup_directory
|
||||||
|
self.latest_backup_file_name = backup_file_name
|
||||||
|
return True
|
||||||
|
|
||||||
|
self.sys_log.error("Unable to create database backup.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def restore_backup(self, backup_directory: Optional[str] = None, backup_file_name: Optional[str] = None) -> bool:
|
||||||
|
"""
|
||||||
|
Restore a backup from backup server.
|
||||||
|
|
||||||
|
:param: backup_directory: Name of directory where backup will be stored. Optional.
|
||||||
|
:type: backup_directory: Optional[str]
|
||||||
|
|
||||||
|
:param: backup_file_name: Name of file where backup will be stored. Optional.
|
||||||
|
:type: backup_file_name: Optional[str]
|
||||||
|
"""
|
||||||
|
if backup_directory is None:
|
||||||
|
backup_directory = self.latest_backup_directory
|
||||||
|
|
||||||
|
if backup_file_name is None:
|
||||||
|
backup_file_name = self.latest_backup_file_name
|
||||||
|
|
||||||
|
software_manager: SoftwareManager = self.software_manager
|
||||||
|
ftp_client_service: FTPClient = software_manager.software["FTPClient"]
|
||||||
|
|
||||||
|
# retrieve backup file from backup server
|
||||||
|
response = ftp_client_service.request_file(
|
||||||
|
src_folder_name=backup_directory,
|
||||||
|
src_file_name=backup_file_name,
|
||||||
|
dest_folder_name="downloads",
|
||||||
|
dest_file_name="database.db",
|
||||||
|
dest_ip_address=self.backup_server,
|
||||||
|
)
|
||||||
|
|
||||||
|
if response:
|
||||||
|
# replace db file
|
||||||
|
self.file_system.delete_file(folder_name=self.folder.name, file_name="downloads.db")
|
||||||
|
|
||||||
|
self.file_system.move_file(
|
||||||
|
src_folder_name="downloads", src_file_name="database.db", dst_folder_name=self.folder.name
|
||||||
|
)
|
||||||
|
self._db_file = self.file_system.get_file(folder_name=self.folder.name, file_name="database.db")
|
||||||
|
|
||||||
|
return self._db_file is not None
|
||||||
|
|
||||||
|
self.sys_log.error("Unable to restore database backup.")
|
||||||
|
return False
|
||||||
|
|
||||||
def _create_db_file(self):
|
def _create_db_file(self):
|
||||||
"""Creates the Simulation File and sqlite file in the file system."""
|
"""Creates the Simulation File and sqlite file in the file system."""
|
||||||
self._db_file: File = self.file_system.create_file(folder_name="database", file_name="database.db", real=True)
|
self._db_file: File = self.file_system.create_file(folder_name="database", file_name="database.db", real=True)
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class FTPClient(FTPServiceABC):
|
|||||||
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port
|
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port
|
||||||
)
|
)
|
||||||
if payload.status_code == FTPStatusCode.OK:
|
if payload.status_code == FTPStatusCode.OK:
|
||||||
self.connected = False
|
self.connected = None
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -159,8 +159,9 @@ class FTPClient(FTPServiceABC):
|
|||||||
if not self.connected:
|
if not self.connected:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
self.sys_log.info(f"Sending file {src_folder_name}/{src_file_name} to {str(dest_ip_address)}")
|
||||||
# send STOR request
|
# send STOR request
|
||||||
self._send_data(
|
return self._send_data(
|
||||||
file=file_to_transfer,
|
file=file_to_transfer,
|
||||||
dest_folder_name=dest_folder_name,
|
dest_folder_name=dest_folder_name,
|
||||||
dest_file_name=dest_file_name,
|
dest_file_name=dest_file_name,
|
||||||
@@ -168,9 +169,6 @@ class FTPClient(FTPServiceABC):
|
|||||||
dest_port=dest_port,
|
dest_port=dest_port,
|
||||||
)
|
)
|
||||||
|
|
||||||
# send disconnect
|
|
||||||
return self._disconnect_from_server(dest_ip_address=dest_ip_address, dest_port=dest_port)
|
|
||||||
|
|
||||||
def request_file(
|
def request_file(
|
||||||
self,
|
self,
|
||||||
dest_ip_address: IPv4Address,
|
dest_ip_address: IPv4Address,
|
||||||
@@ -222,14 +220,12 @@ class FTPClient(FTPServiceABC):
|
|||||||
"dest_folder_name": dest_folder_name,
|
"dest_folder_name": dest_folder_name,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
self.sys_log.info(f"Requesting file {src_folder_name}/{src_file_name} from {str(dest_ip_address)}")
|
||||||
software_manager: SoftwareManager = self.software_manager
|
software_manager: SoftwareManager = self.software_manager
|
||||||
software_manager.send_payload_to_session_manager(
|
software_manager.send_payload_to_session_manager(
|
||||||
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port
|
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port
|
||||||
)
|
)
|
||||||
|
|
||||||
# send disconnect
|
|
||||||
self._disconnect_from_server(dest_ip_address=dest_ip_address, dest_port=dest_port)
|
|
||||||
|
|
||||||
# the payload should have ok status code
|
# the payload should have ok status code
|
||||||
if payload.status_code == FTPStatusCode.OK:
|
if payload.status_code == FTPStatusCode.OK:
|
||||||
self.sys_log.info(f"File {src_folder_name}/{src_file_name} found in FTP server.")
|
self.sys_log.info(f"File {src_folder_name}/{src_file_name} found in FTP server.")
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ class FTPServer(FTPServiceABC):
|
|||||||
if session_id:
|
if session_id:
|
||||||
session_details = self._get_session_details(session_id)
|
session_details = self._get_session_details(session_id)
|
||||||
|
|
||||||
|
if payload.ftp_command is not None:
|
||||||
|
self.sys_log.info(f"Received FTP {payload.ftp_command.name} command.")
|
||||||
|
|
||||||
# process server specific commands, otherwise call super
|
# process server specific commands, otherwise call super
|
||||||
if payload.ftp_command == FTPCommand.PORT:
|
if payload.ftp_command == FTPCommand.PORT:
|
||||||
# check that the port is valid
|
# check that the port is valid
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ class FTPServiceABC(Service, ABC):
|
|||||||
:param: session_id: session ID linked to the FTP Packet. Optional.
|
:param: session_id: session ID linked to the FTP Packet. Optional.
|
||||||
:type: session_id: Optional[str]
|
:type: session_id: Optional[str]
|
||||||
"""
|
"""
|
||||||
|
if payload.ftp_command is not None:
|
||||||
|
self.sys_log.info(f"Received FTP {payload.ftp_command.name} command.")
|
||||||
|
|
||||||
# handle STOR request
|
# handle STOR request
|
||||||
if payload.ftp_command == FTPCommand.STOR:
|
if payload.ftp_command == FTPCommand.STOR:
|
||||||
# check that the file is created in the computed hosting the FTP server
|
# check that the file is created in the computed hosting the FTP server
|
||||||
@@ -48,11 +51,7 @@ class FTPServiceABC(Service, ABC):
|
|||||||
file_name = payload.ftp_command_args["dest_file_name"]
|
file_name = payload.ftp_command_args["dest_file_name"]
|
||||||
folder_name = payload.ftp_command_args["dest_folder_name"]
|
folder_name = payload.ftp_command_args["dest_folder_name"]
|
||||||
file_size = payload.ftp_command_args["file_size"]
|
file_size = payload.ftp_command_args["file_size"]
|
||||||
self.file_system.create_file(
|
self.file_system.create_file(file_name=file_name, folder_name=folder_name, size=file_size, real=True)
|
||||||
file_name=file_name,
|
|
||||||
folder_name=folder_name,
|
|
||||||
size=file_size,
|
|
||||||
)
|
|
||||||
self.sys_log.info(
|
self.sys_log.info(
|
||||||
f"Created item in {self.sys_log.hostname}: {payload.ftp_command_args['dest_folder_name']}/"
|
f"Created item in {self.sys_log.hostname}: {payload.ftp_command_args['dest_folder_name']}/"
|
||||||
f"{payload.ftp_command_args['dest_file_name']}"
|
f"{payload.ftp_command_args['dest_file_name']}"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from ipaddress import IPv4Address
|
|||||||
from primaite.simulator.network.hardware.nodes.server import Server
|
from primaite.simulator.network.hardware.nodes.server import Server
|
||||||
from primaite.simulator.system.applications.database_client import DatabaseClient
|
from primaite.simulator.system.applications.database_client import DatabaseClient
|
||||||
from primaite.simulator.system.services.database.database_service import DatabaseService
|
from primaite.simulator.system.services.database.database_service import DatabaseService
|
||||||
|
from primaite.simulator.system.services.ftp.ftp_server import FTPServer
|
||||||
|
|
||||||
|
|
||||||
def test_database_client_server_connection(uc2_network):
|
def test_database_client_server_connection(uc2_network):
|
||||||
@@ -57,3 +58,30 @@ def test_database_client_query(uc2_network):
|
|||||||
db_client.connect()
|
db_client.connect()
|
||||||
|
|
||||||
assert db_client.query("SELECT * FROM user;")
|
assert db_client.query("SELECT * FROM user;")
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_database_backup(uc2_network):
|
||||||
|
"""Run the backup_database method and check if the FTP server has the relevant file."""
|
||||||
|
db_server: Server = uc2_network.get_node_by_hostname("database_server")
|
||||||
|
db_service: DatabaseService = db_server.software_manager.software["DatabaseService"]
|
||||||
|
|
||||||
|
# back up should be created
|
||||||
|
assert db_service.backup_database(backup_file_name="test_file.db") is True
|
||||||
|
|
||||||
|
backup_server: Server = uc2_network.get_node_by_hostname("backup_server")
|
||||||
|
ftp_server: FTPServer = backup_server.software_manager.software["FTPServer"]
|
||||||
|
|
||||||
|
# backup file should exist in the backup server
|
||||||
|
assert ftp_server.file_system.get_file(folder_name="db_backup", file_name="test_file.db") is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_restore_backup(uc2_network):
|
||||||
|
"""Run the restore_backup method and check if the backup is properly restored."""
|
||||||
|
db_server: Server = uc2_network.get_node_by_hostname("database_server")
|
||||||
|
db_service: DatabaseService = db_server.software_manager.software["DatabaseService"]
|
||||||
|
|
||||||
|
# create a back up
|
||||||
|
assert db_service.backup_database(backup_file_name="test_file.db") is True
|
||||||
|
|
||||||
|
# back up should be restored
|
||||||
|
assert db_service.restore_backup(backup_file_name="test_file.db") is True
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def test_ftp_client_store_file_in_server(uc2_network):
|
|||||||
# create file on ftp client
|
# create file on ftp client
|
||||||
ftp_client.file_system.create_file(file_name="test_file.txt")
|
ftp_client.file_system.create_file(file_name="test_file.txt")
|
||||||
|
|
||||||
ftp_client.send_file(
|
assert ftp_client.send_file(
|
||||||
src_folder_name="root",
|
src_folder_name="root",
|
||||||
src_file_name="test_file.txt",
|
src_file_name="test_file.txt",
|
||||||
dest_folder_name="client_1_backup",
|
dest_folder_name="client_1_backup",
|
||||||
@@ -50,7 +50,7 @@ def test_ftp_client_retrieve_file_from_server(uc2_network):
|
|||||||
# create file on ftp server
|
# create file on ftp server
|
||||||
ftp_server.file_system.create_file(file_name="test_file.txt", folder_name="file_share")
|
ftp_server.file_system.create_file(file_name="test_file.txt", folder_name="file_share")
|
||||||
|
|
||||||
ftp_client.request_file(
|
assert ftp_client.request_file(
|
||||||
src_folder_name="file_share",
|
src_folder_name="file_share",
|
||||||
src_file_name="test_file.txt",
|
src_file_name="test_file.txt",
|
||||||
dest_folder_name="downloads",
|
dest_folder_name="downloads",
|
||||||
|
|||||||
Reference in New Issue
Block a user