#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:
Czar.Echavez
2023-09-28 12:23:49 +01:00
parent c096d06bcd
commit 6202d320a6
8 changed files with 146 additions and 16 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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.")

View File

@@ -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

View File

@@ -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']}"

View File

@@ -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

View File

@@ -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",