#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:
if self.is_up:
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
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_service: DatabaseService = database_server.software_manager.software["DatabaseService"] # noqa
database_service.start()
database_service.configure_backup(backup_server=IPv4Address("192.168.1.16"))
database_service._process_sql(ddl, None) # noqa
for insert_statement in user_insert_statements:
database_service._process_sql(insert_statement, None) # noqa

View File

@@ -1,5 +1,6 @@
import sqlite3
from datetime import datetime
from ipaddress import IPv4Address
from sqlite3 import OperationalError
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.transport_layer import Port
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.software import SoftwareHealthState
@@ -23,6 +25,15 @@ class DatabaseService(Service):
password: Optional[str] = None
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):
kwargs["name"] = "DatabaseService"
kwargs["port"] = Port.POSTGRES_SERVER
@@ -58,6 +69,96 @@ class DatabaseService(Service):
table.add_row([row])
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):
"""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)

View File

@@ -107,7 +107,7 @@ class FTPClient(FTPServiceABC):
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port
)
if payload.status_code == FTPStatusCode.OK:
self.connected = False
self.connected = None
return True
return False
@@ -159,8 +159,9 @@ class FTPClient(FTPServiceABC):
if not self.connected:
return False
else:
self.sys_log.info(f"Sending file {src_folder_name}/{src_file_name} to {str(dest_ip_address)}")
# send STOR request
self._send_data(
return self._send_data(
file=file_to_transfer,
dest_folder_name=dest_folder_name,
dest_file_name=dest_file_name,
@@ -168,9 +169,6 @@ class FTPClient(FTPServiceABC):
dest_port=dest_port,
)
# send disconnect
return self._disconnect_from_server(dest_ip_address=dest_ip_address, dest_port=dest_port)
def request_file(
self,
dest_ip_address: IPv4Address,
@@ -222,14 +220,12 @@ class FTPClient(FTPServiceABC):
"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.send_payload_to_session_manager(
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
if payload.status_code == FTPStatusCode.OK:
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:
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
if payload.ftp_command == FTPCommand.PORT:
# 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.
: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
if payload.ftp_command == FTPCommand.STOR:
# 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"]
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.file_system.create_file(file_name=file_name, folder_name=folder_name, size=file_size, real=True)
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']}"

View File

@@ -3,6 +3,7 @@ from ipaddress import IPv4Address
from primaite.simulator.network.hardware.nodes.server import Server
from primaite.simulator.system.applications.database_client import DatabaseClient
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):
@@ -57,3 +58,30 @@ def test_database_client_query(uc2_network):
db_client.connect()
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
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_file_name="test_file.txt",
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
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_file_name="test_file.txt",
dest_folder_name="downloads",