#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

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