Merged PR 378: Add create file/folder and file access requests

## Summary
Added requests in file system that allows:
- file to be created
- folder to be created
- file to be "accessed"

## Test process
https://dev.azure.com/ma-dev-uk/PrimAITE/_git/PrimAITE/pullrequest/378?_a=files&path=/tests/integration_tests/test_simulation/test_request_response.py

## Checklist
- [X] PR is linked to a **work item**
- [X] **acceptance criteria** of linked ticket are met
- [X] performed **self-review** of the code
- [X] written **tests** for any new functionality added with this PR
- [ ] updated the **documentation** if this PR changes or adds functionality
- [ ] written/updated **design docs** if this PR implements new functionality
- [ ] updated the **change log**
- [X] ran **pre-commit** checks for code style
- [ ] attended to any **TO-DOs** left in the code

Related work items: #2606
This commit is contained in:
Czar Echavez
2024-05-21 10:35:49 +00:00
5 changed files with 233 additions and 1 deletions

View File

@@ -313,6 +313,36 @@ class NodeFolderRestoreAction(NodeFolderAbstractAction):
self.verb: str = "restore" self.verb: str = "restore"
class NodeFileCreateAction(AbstractAction):
"""Action which creates a new file in a given folder."""
def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, **kwargs) -> None:
super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, **kwargs)
self.verb: str = "create"
def form_request(self, node_id: int, folder_name: str, file_name: str) -> List[str]:
"""Return the action formatted as a request which can be ingested by the PrimAITE simulation."""
node_name = self.manager.get_node_name_by_idx(node_id)
if node_name is None or folder_name is None or file_name is None:
return ["do_nothing"]
return ["network", "node", node_name, "file_system", "create", "file", folder_name, file_name]
class NodeFolderCreateAction(AbstractAction):
"""Action which creates a new folder."""
def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, **kwargs) -> None:
super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, **kwargs)
self.verb: str = "create"
def form_request(self, node_id: int, folder_name: str) -> List[str]:
"""Return the action formatted as a request which can be ingested by the PrimAITE simulation."""
node_name = self.manager.get_node_name_by_idx(node_id)
if node_name is None or folder_name is None:
return ["do_nothing"]
return ["network", "node", node_name, "file_system", "create", "folder", folder_name]
class NodeFileAbstractAction(AbstractAction): class NodeFileAbstractAction(AbstractAction):
"""Abstract base class for file actions. """Abstract base class for file actions.
@@ -393,6 +423,21 @@ class NodeFileCorruptAction(NodeFileAbstractAction):
self.verb: str = "corrupt" self.verb: str = "corrupt"
class NodeFileAccessAction(AbstractAction):
"""Action which increases a file's access count."""
def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, **kwargs) -> None:
super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, **kwargs)
self.verb: str = "access"
def form_request(self, node_id: int, folder_name: str, file_name: str) -> List[str]:
"""Return the action formatted as a request which can be ingested by the PrimAITE simulation."""
node_name = self.manager.get_node_name_by_idx(node_id)
if node_name is None or folder_name is None or file_name is None:
return ["do_nothing"]
return ["network", "node", node_name, "file_system", "access", folder_name, file_name]
class NodeAbstractAction(AbstractAction): class NodeAbstractAction(AbstractAction):
""" """
Abstract base class for node actions. Abstract base class for node actions.
@@ -846,11 +891,14 @@ class ActionManager:
"NODE_APPLICATION_INSTALL": NodeApplicationInstallAction, "NODE_APPLICATION_INSTALL": NodeApplicationInstallAction,
"NODE_APPLICATION_REMOVE": NodeApplicationRemoveAction, "NODE_APPLICATION_REMOVE": NodeApplicationRemoveAction,
"NODE_FILE_SCAN": NodeFileScanAction, "NODE_FILE_SCAN": NodeFileScanAction,
"NODE_FILE_CREATE": NodeFileCreateAction,
"NODE_FILE_CHECKHASH": NodeFileCheckhashAction, "NODE_FILE_CHECKHASH": NodeFileCheckhashAction,
"NODE_FILE_DELETE": NodeFileDeleteAction, "NODE_FILE_DELETE": NodeFileDeleteAction,
"NODE_FILE_REPAIR": NodeFileRepairAction, "NODE_FILE_REPAIR": NodeFileRepairAction,
"NODE_FILE_RESTORE": NodeFileRestoreAction, "NODE_FILE_RESTORE": NodeFileRestoreAction,
"NODE_FILE_CORRUPT": NodeFileCorruptAction, "NODE_FILE_CORRUPT": NodeFileCorruptAction,
"NODE_FILE_ACCESS": NodeFileAccessAction,
"NODE_FOLDER_CREATE": NodeFolderCreateAction,
"NODE_FOLDER_SCAN": NodeFolderScanAction, "NODE_FOLDER_SCAN": NodeFolderScanAction,
"NODE_FOLDER_CHECKHASH": NodeFolderCheckhashAction, "NODE_FOLDER_CHECKHASH": NodeFolderCheckhashAction,
"NODE_FOLDER_REPAIR": NodeFolderRepairAction, "NODE_FOLDER_REPAIR": NodeFolderRepairAction,

View File

@@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path from pathlib import Path
from typing import Dict, Optional from typing import Any, Dict, List, Optional
from prettytable import MARKDOWN, PrettyTable from prettytable import MARKDOWN, PrettyTable
@@ -63,6 +63,65 @@ class FileSystem(SimComponent):
request_type=RequestType(func=self._delete_manager), request_type=RequestType(func=self._delete_manager),
) )
self._create_manager = RequestManager()
def _create_file_action(request: List[Any], context: Any) -> RequestResponse:
file = self.create_file(folder_name=request[0], file_name=request[1])
if not file:
return RequestResponse.from_bool(False)
return RequestResponse(
status="success",
data={
"file_name": file.name,
"folder_name": file.folder_name,
"file_type": file.file_type,
"file_size": file.size,
},
)
self._create_manager.add_request(
name="file",
request_type=RequestType(func=_create_file_action),
)
def _create_folder_action(request: List[Any], context: Any) -> RequestResponse:
folder = self.create_folder(folder_name=request[0])
if not folder:
return RequestResponse.from_bool(False)
return RequestResponse(status="success", data={"folder_name": folder.name})
self._create_manager.add_request(
name="folder",
request_type=RequestType(func=_create_folder_action),
)
rm.add_request(
name="create",
request_type=RequestType(func=self._create_manager),
)
def _access_file_action(request: List[Any], context: Any) -> RequestResponse:
file = self.get_file(folder_name=request[0], file_name=request[1])
if not file:
return RequestResponse.from_bool(False)
if self.access_file(folder_name=request[0], file_name=request[1]):
return RequestResponse(
status="success",
data={
"file_name": file.name,
"folder_name": file.folder_name,
"file_type": file.file_type,
"file_size": file.size,
"file_status": file.health_status,
},
)
return RequestResponse.from_bool(False)
rm.add_request(
name="access",
request_type=RequestType(func=_access_file_action),
)
self._restore_manager = RequestManager() self._restore_manager = RequestManager()
self._restore_manager.add_request( self._restore_manager.add_request(
name="file", name="file",
@@ -494,3 +553,28 @@ class FileSystem(SimComponent):
return False return False
return folder.restore_file(file_name=file_name) return folder.restore_file(file_name=file_name)
def access_file(self, folder_name: str, file_name: str) -> bool:
"""
Access a file.
Used by agents to simulate a file being accessed.
:param: folder_name: name of the folder where the file is stored
:type: folder_name: str
:param: file_name: name of the file to access
:type: file_name: str
"""
folder = self.get_folder(folder_name=folder_name)
if folder:
file = folder.get_file(file_name=file_name)
if file:
file.num_access += 1
return True
else:
self.sys_log.error(f"Unable to access file that does not exist. (file name: {file_name})")
return False

View File

@@ -431,12 +431,15 @@ def game_and_agent():
{"type": "NODE_APPLICATION_FIX"}, {"type": "NODE_APPLICATION_FIX"},
{"type": "NODE_APPLICATION_INSTALL"}, {"type": "NODE_APPLICATION_INSTALL"},
{"type": "NODE_APPLICATION_REMOVE"}, {"type": "NODE_APPLICATION_REMOVE"},
{"type": "NODE_FILE_CREATE"},
{"type": "NODE_FILE_SCAN"}, {"type": "NODE_FILE_SCAN"},
{"type": "NODE_FILE_CHECKHASH"}, {"type": "NODE_FILE_CHECKHASH"},
{"type": "NODE_FILE_DELETE"}, {"type": "NODE_FILE_DELETE"},
{"type": "NODE_FILE_REPAIR"}, {"type": "NODE_FILE_REPAIR"},
{"type": "NODE_FILE_RESTORE"}, {"type": "NODE_FILE_RESTORE"},
{"type": "NODE_FILE_CORRUPT"}, {"type": "NODE_FILE_CORRUPT"},
{"type": "NODE_FILE_ACCESS"},
{"type": "NODE_FOLDER_CREATE"},
{"type": "NODE_FOLDER_SCAN"}, {"type": "NODE_FOLDER_SCAN"},
{"type": "NODE_FOLDER_CHECKHASH"}, {"type": "NODE_FOLDER_CHECKHASH"},
{"type": "NODE_FOLDER_REPAIR"}, {"type": "NODE_FOLDER_REPAIR"},

View File

@@ -329,6 +329,78 @@ def test_node_file_delete_integration(game_and_agent: Tuple[PrimaiteGame, ProxyA
assert file.deleted assert file.deleted
def test_node_file_create(game_and_agent: Tuple[PrimaiteGame, ProxyAgent]):
"""Test that a file is created."""
game, agent = game_and_agent
client_1 = game.simulation.network.get_node_by_hostname("client_1") #
action = (
"NODE_FILE_CREATE",
{
"node_id": 0,
"folder_name": "test",
"file_name": "file.txt",
},
)
agent.store_action(action)
game.step()
assert client_1.file_system.get_file(folder_name="test", file_name="file.txt")
def test_node_file_access(game_and_agent: Tuple[PrimaiteGame, ProxyAgent]):
"""Test that the file access increments."""
game, agent = game_and_agent
client_1 = game.simulation.network.get_node_by_hostname("client_1") #
action = (
"NODE_FILE_CREATE",
{
"node_id": 0,
"folder_name": "test",
"file_name": "file.txt",
},
)
agent.store_action(action)
game.step()
assert client_1.file_system.get_file(folder_name="test", file_name="file.txt").num_access == 0
action = (
"NODE_FILE_ACCESS",
{
"node_id": 0,
"folder_name": "test",
"file_name": "file.txt",
},
)
agent.store_action(action)
game.step()
assert client_1.file_system.get_file(folder_name="test", file_name="file.txt").num_access == 1
def test_node_folder_create(game_and_agent: Tuple[PrimaiteGame, ProxyAgent]):
"""Test that a folder is created."""
game, agent = game_and_agent
client_1 = game.simulation.network.get_node_by_hostname("client_1") #
action = (
"NODE_FOLDER_CREATE",
{
"node_id": 0,
"folder_name": "test",
},
)
agent.store_action(action)
game.step()
assert client_1.file_system.get_folder(folder_name="test")
def test_network_router_port_disable_integration(game_and_agent: Tuple[PrimaiteGame, ProxyAgent]): def test_network_router_port_disable_integration(game_and_agent: Tuple[PrimaiteGame, ProxyAgent]):
"""Test that the NetworkPortDisableAction can form a request and that it is accepted by the simulation.""" """Test that the NetworkPortDisableAction can form a request and that it is accepted by the simulation."""
game, agent = game_and_agent game, agent = game_and_agent

View File

@@ -15,6 +15,31 @@ from primaite.simulator.network.transmission.transport_layer import Port
from tests.conftest import TestApplication, TestService from tests.conftest import TestApplication, TestService
def test_successful_node_file_system_creation_request(example_network):
"""Tests that the file system requests."""
client_1 = example_network.get_node_by_hostname("client_1")
assert client_1.file_system.get_file(folder_name="root", file_name="test.txt") is None
response = example_network.apply_request(["node", "client_1", "file_system", "create", "file", "", "test.txt"])
assert response
assert client_1.file_system.get_file(folder_name="root", file_name="test.txt")
assert client_1.file_system.get_folder(folder_name="test_folder") is None
response = example_network.apply_request(["node", "client_1", "file_system", "create", "folder", "test_folder"])
assert response
assert client_1.file_system.get_folder(folder_name="test_folder")
assert client_1.file_system.get_file(folder_name="root", file_name="test.txt").num_access == 0
response = example_network.apply_request(["node", "client_1", "file_system", "access", "root", "test.txt"])
assert response
assert client_1.file_system.get_file(folder_name="root", file_name="test.txt").num_access == 1
def test_successful_application_requests(example_network): def test_successful_application_requests(example_network):
net = example_network net = example_network