diff --git a/src/primaite/simulator/file_system/file_system.py b/src/primaite/simulator/file_system/file_system.py index 440b7dc5..1346d3e0 100644 --- a/src/primaite/simulator/file_system/file_system.py +++ b/src/primaite/simulator/file_system/file_system.py @@ -1,5 +1,5 @@ from random import choice -from typing import Dict, Optional +from typing import Dict, Optional, Union from primaite import getLogger from primaite.simulator.core import SimComponent @@ -211,7 +211,7 @@ class FileSystem(SimComponent): if file is not None: return file - def get_folder_by_name(self, folder_name: str) -> FileSystemFolder: + def get_folder_by_name(self, folder_name: str) -> Union[FileSystemFolder, None]: """ Returns a the first folder with a matching name. diff --git a/src/primaite/simulator/network/hardware/base.py b/src/primaite/simulator/network/hardware/base.py index a68ff480..e3e38f86 100644 --- a/src/primaite/simulator/network/hardware/base.py +++ b/src/primaite/simulator/network/hardware/base.py @@ -1004,6 +1004,7 @@ class Node(SimComponent): if service in self: _LOGGER.warning(f"Can't add service {service.uuid} to node {self.uuid}. It's already installed.") return + self.services[service.uuid] = service service.parent = self service.install() # Perform any additional setup, such as creating files for this service on the node. _LOGGER.info(f"Added service {service.uuid} to node {self.uuid}") diff --git a/src/primaite/simulator/system/services/database.py b/src/primaite/simulator/system/services/database.py index 0d1de15c..554455b8 100644 --- a/src/primaite/simulator/system/services/database.py +++ b/src/primaite/simulator/system/services/database.py @@ -12,6 +12,21 @@ class DatabaseService(Service): """TODO.""" return super().describe_state() + def uninstall(self) -> None: + """ + Undo installation procedure. + + This method deletes files created when installing the database, and the database folder if it is empty. + """ + super().uninstall() + node: Node = self.parent + node.file_system.delete_file(self.primary_store) + node.file_system.delete_file(self.transaction_log) + if self.secondary_store: + node.file_system.delete_file(self.secondary_store) + if len(self.folder.files) == 0: + node.file_system.delete_folder(self.folder) + def install(self) -> None: """Perform first time install on a node, creating necessary files.""" super().install() @@ -39,10 +54,16 @@ class DatabaseService(Service): # note that this parent.file_system.create_folder call in the future will be authenticated by using permissions # handler. This permission will be granted based on service account given to the database service. self.parent: Node - folder = self.parent.file_system.create_folder(folder_name) - self.parent.file_system.create_file("db_primary_store", db_size, FileSystemFileType.MDF, folder=folder) - self.parent.file_system.create_file("db_transaction_log", "1", FileSystemFileType.LDF, folder=folder) + self.folder = self.parent.file_system.create_folder(folder_name) + self.primary_store = self.parent.file_system.create_file( + "db_primary_store", db_size, FileSystemFileType.MDF, folder=self.folder + ) + self.transaction_log = self.parent.file_system.create_file( + "db_transaction_log", "1", FileSystemFileType.LDF, folder=self.folder + ) if use_secondary_db_file: - self.parent.file_system.create_file( - "db_secondary_store", secondary_db_size, FileSystemFileType.NDF, folder=folder + self.secondary_store = self.parent.file_system.create_file( + "db_secondary_store", secondary_db_size, FileSystemFileType.NDF, folder=self.folder ) + else: + self.secondary_store = None diff --git a/src/primaite/simulator/system/software.py b/src/primaite/simulator/system/software.py index 17eaee3d..1fcdb522 100644 --- a/src/primaite/simulator/system/software.py +++ b/src/primaite/simulator/system/software.py @@ -1,13 +1,10 @@ from abc import abstractmethod from enum import Enum -from typing import Any, Dict, Set, TYPE_CHECKING +from typing import Any, Dict, Set from primaite.simulator.core import Action, ActionManager, SimComponent from primaite.simulator.network.transmission.transport_layer import Port -if TYPE_CHECKING: - from primaite.simulator.network.hardware.base import Node - class SoftwareType(Enum): """ @@ -143,11 +140,17 @@ class Software(SimComponent): This is an abstract class that should be overwritten by specific applications or services. It must be called after the service is already associate with a node. For example, a service may need to authenticate with a server during installation, or create files in the node's filesystem. - - :param node: Node on which this software runs. - :type node: Node """ - parent: "Node" = self.parent # noqa + pass + + def uninstall(self) -> None: + """Uninstall this service from a node. + + This is an abstract class that should be overwritten by applications or services. It must be called after the + `install` method has already been run on that node. It should undo any installation steps, for example by + deleting files, or contacting a server. + """ + pass def scan(self) -> None: """Update the observed health status to match the actual health status.""" diff --git a/tests/integration_tests/system/test_database_on_node.py b/tests/integration_tests/system/test_database_on_node.py index f295eaf1..73d19339 100644 --- a/tests/integration_tests/system/test_database_on_node.py +++ b/tests/integration_tests/system/test_database_on_node.py @@ -20,3 +20,37 @@ def test_installing_database(): node = Node(hostname="db-server") node.install_service(db) + + assert db in node + + file_exists = False + for folder in node.file_system.folders.values(): + for file in folder.files.values(): + if file.name == "db_primary_store": + file_exists = True + break + if file_exists: + break + assert file_exists + + +def test_uninstalling_database(): + db = DatabaseService( + name="SQL-database", + health_state_actual=SoftwareHealthState.GOOD, + health_state_visible=SoftwareHealthState.GOOD, + criticality=SoftwareCriticality.MEDIUM, + ports=[ + Port.SQL_SERVER, + ], + operating_state=ServiceOperatingState.RUNNING, + ) + + node = Node(hostname="db-server") + + node.install_service(db) + + node.uninstall_service(db) + + assert db not in node + assert node.file_system.get_folder_by_name("database") is None