#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:
|
||||
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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']}"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user