Merge branch 'dev' into feature/2712-Terminal_Login_Handling
This commit is contained in:
@@ -9,6 +9,9 @@ io_settings:
|
||||
save_pcap_logs: true
|
||||
save_sys_logs: true
|
||||
sys_log_level: WARNING
|
||||
agent_log_level: INFO
|
||||
save_agent_logs: true
|
||||
write_agent_log_to_terminal: True
|
||||
|
||||
|
||||
game:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -41,6 +41,12 @@ agents:
|
||||
options:
|
||||
source_node: client_1
|
||||
target_ip_address: 192.168.10.0/24
|
||||
target_port:
|
||||
- 21
|
||||
- 53
|
||||
- 80
|
||||
- 123
|
||||
- 219
|
||||
|
||||
reward_function:
|
||||
reward_components:
|
||||
|
||||
@@ -177,6 +177,9 @@ simulation:
|
||||
default_gateway: 192.168.10.1
|
||||
dns_server: 192.168.1.10
|
||||
applications:
|
||||
- type: NMAP
|
||||
options:
|
||||
fix_duration: 1
|
||||
- type: RansomwareScript
|
||||
options:
|
||||
fix_duration: 1
|
||||
|
||||
@@ -243,25 +243,25 @@ agents:
|
||||
action: "NODE_FILE_SCAN"
|
||||
options:
|
||||
node_id: 2
|
||||
folder_id: 1
|
||||
folder_id: 0
|
||||
file_id: 0
|
||||
10:
|
||||
action: "NODE_FILE_CHECKHASH"
|
||||
options:
|
||||
node_id: 2
|
||||
folder_id: 1
|
||||
folder_id: 0
|
||||
file_id: 0
|
||||
11:
|
||||
action: "NODE_FILE_DELETE"
|
||||
options:
|
||||
node_id: 2
|
||||
folder_id: 1
|
||||
folder_id: 0
|
||||
file_id: 0
|
||||
12:
|
||||
action: "NODE_FILE_REPAIR"
|
||||
options:
|
||||
node_id: 2
|
||||
folder_id: 1
|
||||
folder_id: 0
|
||||
file_id: 0
|
||||
13:
|
||||
action: "NODE_SERVICE_FIX"
|
||||
@@ -272,22 +272,22 @@ agents:
|
||||
action: "NODE_FOLDER_SCAN"
|
||||
options:
|
||||
node_id: 2
|
||||
folder_id: 1
|
||||
folder_id: 0
|
||||
15:
|
||||
action: "NODE_FOLDER_CHECKHASH"
|
||||
options:
|
||||
node_id: 2
|
||||
folder_id: 1
|
||||
folder_id: 0
|
||||
16:
|
||||
action: "NODE_FOLDER_REPAIR"
|
||||
options:
|
||||
node_id: 2
|
||||
folder_id: 1
|
||||
folder_id: 0
|
||||
17:
|
||||
action: "NODE_FOLDER_RESTORE"
|
||||
options:
|
||||
node_id: 2
|
||||
folder_id: 1
|
||||
folder_id: 0
|
||||
18:
|
||||
action: "NODE_OS_SCAN"
|
||||
options:
|
||||
@@ -518,11 +518,22 @@ agents:
|
||||
nodes:
|
||||
- node_name: domain_controller
|
||||
- node_name: web_server
|
||||
applications:
|
||||
- application_name: DatabaseClient
|
||||
services:
|
||||
- service_name: WebServer
|
||||
- node_name: database_server
|
||||
folders:
|
||||
- folder_name: database
|
||||
files:
|
||||
- file_name: database.db
|
||||
services:
|
||||
- service_name: DatabaseService
|
||||
- node_name: backup_server
|
||||
- node_name: security_suite
|
||||
- node_name: client_1
|
||||
- node_name: client_2
|
||||
|
||||
max_folders_per_node: 2
|
||||
max_files_per_folder: 2
|
||||
max_services_per_node: 2
|
||||
@@ -557,6 +568,7 @@ agents:
|
||||
|
||||
agent_settings:
|
||||
flatten_obs: true
|
||||
action_masking: true
|
||||
|
||||
|
||||
|
||||
@@ -634,6 +646,8 @@ simulation:
|
||||
dns_server: 192.168.1.10
|
||||
services:
|
||||
- type: DatabaseService
|
||||
options:
|
||||
backup_server_ip: 192.168.1.16
|
||||
|
||||
- type: server
|
||||
hostname: backup_server
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
game:
|
||||
max_episode_length: 256
|
||||
ports:
|
||||
- ARP
|
||||
protocols:
|
||||
- ICMP
|
||||
- TCP
|
||||
- UDP
|
||||
|
||||
simulation:
|
||||
network:
|
||||
airspace:
|
||||
frequency_max_capacity_mbps:
|
||||
WIFI_2_4: 123.45
|
||||
WIFI_5: 0.0
|
||||
nodes:
|
||||
- type: computer
|
||||
hostname: pc_a
|
||||
ip_address: 192.168.0.2
|
||||
subnet_mask: 255.255.255.0
|
||||
default_gateway: 192.168.0.1
|
||||
start_up_duration: 0
|
||||
|
||||
- type: computer
|
||||
hostname: pc_b
|
||||
ip_address: 192.168.2.2
|
||||
subnet_mask: 255.255.255.0
|
||||
default_gateway: 192.168.2.1
|
||||
start_up_duration: 0
|
||||
|
||||
- type: wireless_router
|
||||
hostname: router_1
|
||||
start_up_duration: 0
|
||||
|
||||
router_interface:
|
||||
ip_address: 192.168.0.1
|
||||
subnet_mask: 255.255.255.0
|
||||
|
||||
wireless_access_point:
|
||||
ip_address: 192.168.1.1
|
||||
subnet_mask: 255.255.255.0
|
||||
frequency: WIFI_2_4
|
||||
acl:
|
||||
1:
|
||||
action: PERMIT
|
||||
routes:
|
||||
- address: 192.168.2.0 # PC B subnet
|
||||
subnet_mask: 255.255.255.0
|
||||
next_hop_ip_address: 192.168.1.2
|
||||
metric: 0
|
||||
|
||||
- type: wireless_router
|
||||
hostname: router_2
|
||||
start_up_duration: 0
|
||||
|
||||
router_interface:
|
||||
ip_address: 192.168.2.1
|
||||
subnet_mask: 255.255.255.0
|
||||
|
||||
wireless_access_point:
|
||||
ip_address: 192.168.1.2
|
||||
subnet_mask: 255.255.255.0
|
||||
frequency: WIFI_2_4
|
||||
acl:
|
||||
1:
|
||||
action: PERMIT
|
||||
routes:
|
||||
- address: 192.168.0.0 # PC A subnet
|
||||
subnet_mask: 255.255.255.0
|
||||
next_hop_ip_address: 192.168.1.1
|
||||
metric: 0
|
||||
links:
|
||||
- endpoint_a_hostname: pc_a
|
||||
endpoint_a_port: 1
|
||||
endpoint_b_hostname: router_1
|
||||
endpoint_b_port: 2
|
||||
|
||||
- endpoint_a_hostname: pc_b
|
||||
endpoint_a_port: 1
|
||||
endpoint_b_hostname: router_2
|
||||
endpoint_b_port: 2
|
||||
@@ -0,0 +1,81 @@
|
||||
game:
|
||||
max_episode_length: 256
|
||||
ports:
|
||||
- ARP
|
||||
protocols:
|
||||
- ICMP
|
||||
- TCP
|
||||
- UDP
|
||||
|
||||
simulation:
|
||||
network:
|
||||
airspace:
|
||||
frequency_max_capacity_mbps:
|
||||
WIFI_2_4: 0.0
|
||||
WIFI_5: 0.0
|
||||
nodes:
|
||||
- type: computer
|
||||
hostname: pc_a
|
||||
ip_address: 192.168.0.2
|
||||
subnet_mask: 255.255.255.0
|
||||
default_gateway: 192.168.0.1
|
||||
start_up_duration: 0
|
||||
|
||||
- type: computer
|
||||
hostname: pc_b
|
||||
ip_address: 192.168.2.2
|
||||
subnet_mask: 255.255.255.0
|
||||
default_gateway: 192.168.2.1
|
||||
start_up_duration: 0
|
||||
|
||||
- type: wireless_router
|
||||
hostname: router_1
|
||||
start_up_duration: 0
|
||||
|
||||
router_interface:
|
||||
ip_address: 192.168.0.1
|
||||
subnet_mask: 255.255.255.0
|
||||
|
||||
wireless_access_point:
|
||||
ip_address: 192.168.1.1
|
||||
subnet_mask: 255.255.255.0
|
||||
frequency: WIFI_2_4
|
||||
acl:
|
||||
1:
|
||||
action: PERMIT
|
||||
routes:
|
||||
- address: 192.168.2.0 # PC B subnet
|
||||
subnet_mask: 255.255.255.0
|
||||
next_hop_ip_address: 192.168.1.2
|
||||
metric: 0
|
||||
|
||||
- type: wireless_router
|
||||
hostname: router_2
|
||||
start_up_duration: 0
|
||||
|
||||
router_interface:
|
||||
ip_address: 192.168.2.1
|
||||
subnet_mask: 255.255.255.0
|
||||
|
||||
wireless_access_point:
|
||||
ip_address: 192.168.1.2
|
||||
subnet_mask: 255.255.255.0
|
||||
frequency: WIFI_2_4
|
||||
acl:
|
||||
1:
|
||||
action: PERMIT
|
||||
routes:
|
||||
- address: 192.168.0.0 # PC A subnet
|
||||
subnet_mask: 255.255.255.0
|
||||
next_hop_ip_address: 192.168.1.1
|
||||
metric: 0
|
||||
links:
|
||||
- endpoint_a_hostname: pc_a
|
||||
endpoint_a_port: 1
|
||||
endpoint_b_hostname: router_1
|
||||
endpoint_b_port: 2
|
||||
|
||||
- endpoint_a_hostname: pc_b
|
||||
endpoint_a_port: 1
|
||||
endpoint_b_hostname: router_2
|
||||
endpoint_b_port: 2
|
||||
@@ -3,6 +3,7 @@ from typing import Any, Dict, Tuple
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
from ray import init as rayinit
|
||||
|
||||
from primaite import getLogger, PRIMAITE_PATHS
|
||||
from primaite.game.agent.actions import ActionManager
|
||||
@@ -29,6 +30,7 @@ from primaite.simulator.system.services.service import Service
|
||||
from primaite.simulator.system.services.web_server.web_server import WebServer
|
||||
from tests import TEST_ASSETS_ROOT
|
||||
|
||||
rayinit(local_mode=True)
|
||||
ACTION_SPACE_NODE_VALUES = 1
|
||||
ACTION_SPACE_NODE_ACTION_VALUES = 1
|
||||
|
||||
@@ -87,7 +89,10 @@ def service_class():
|
||||
@pytest.fixture(scope="function")
|
||||
def application(file_system) -> DummyApplication:
|
||||
return DummyApplication(
|
||||
name="DummyApplication", port=Port.ARP, file_system=file_system, sys_log=SysLog(hostname="dummy_application")
|
||||
name="DummyApplication",
|
||||
port=Port.ARP,
|
||||
file_system=file_system,
|
||||
sys_log=SysLog(hostname="dummy_application"),
|
||||
)
|
||||
|
||||
|
||||
@@ -252,8 +257,7 @@ def example_network() -> Network:
|
||||
server_2.power_on()
|
||||
network.connect(endpoint_b=server_2.network_interface[1], endpoint_a=switch_1.network_interface[2])
|
||||
|
||||
router_1.acl.add_rule(action=ACLAction.PERMIT, src_port=Port.ARP, dst_port=Port.ARP, position=22)
|
||||
router_1.acl.add_rule(action=ACLAction.PERMIT, protocol=IPProtocol.ICMP, position=23)
|
||||
router_1.acl.add_rule(action=ACLAction.PERMIT, position=1)
|
||||
|
||||
assert all(link.is_up for link in network.links.values())
|
||||
|
||||
|
||||
1
tests/e2e_integration_tests/action_masking/__init__.py
Normal file
1
tests/e2e_integration_tests/action_masking/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
@@ -0,0 +1,156 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
from typing import Dict
|
||||
|
||||
import yaml
|
||||
from ray.rllib.algorithms.ppo import PPOConfig
|
||||
from ray.rllib.core.rl_module.marl_module import MultiAgentRLModuleSpec
|
||||
from ray.rllib.core.rl_module.rl_module import SingleAgentRLModuleSpec
|
||||
from ray.rllib.examples.rl_modules.classes.action_masking_rlm import ActionMaskingTorchRLModule
|
||||
from sb3_contrib import MaskablePPO
|
||||
|
||||
from primaite.game.game import PrimaiteGame
|
||||
from primaite.session.environment import PrimaiteGymEnv
|
||||
from primaite.session.ray_envs import PrimaiteRayEnv, PrimaiteRayMARLEnv
|
||||
from tests import TEST_ASSETS_ROOT
|
||||
|
||||
CFG_PATH = TEST_ASSETS_ROOT / "configs/test_primaite_session.yaml"
|
||||
MARL_PATH = TEST_ASSETS_ROOT / "configs/multi_agent_session.yaml"
|
||||
|
||||
|
||||
def test_sb3_action_masking(monkeypatch):
|
||||
# There's no simple way of capturing what the action mask was at every step, therefore we are mocking the action
|
||||
# mask function here to save the output of the action mask method and pass through the result back to the agent.
|
||||
old_action_mask_method = PrimaiteGame.action_mask
|
||||
mask_history = []
|
||||
|
||||
def cache_action_mask(obj, agent_name):
|
||||
mask = old_action_mask_method(obj, agent_name)
|
||||
mask_history.append(mask)
|
||||
return mask
|
||||
|
||||
# Even though it's easy to know which CAOS action the agent took by looking at agent history, we don't know which
|
||||
# action map action integer that was, therefore we cache it by using monkeypatch
|
||||
action_num_history = []
|
||||
|
||||
def cache_step(env, action: int):
|
||||
action_num_history.append(action)
|
||||
return PrimaiteGymEnv.step(env, action)
|
||||
|
||||
monkeypatch.setattr(PrimaiteGame, "action_mask", cache_action_mask)
|
||||
env = PrimaiteGymEnv(CFG_PATH)
|
||||
monkeypatch.setattr(env, "step", lambda action: cache_step(env, action))
|
||||
|
||||
model = MaskablePPO("MlpPolicy", env, gamma=0.4, seed=32, batch_size=32)
|
||||
model.learn(256)
|
||||
|
||||
assert len(action_num_history) == len(mask_history) > 0
|
||||
# Make sure the masks had at least some False entries, if it was all True then the mask was disabled
|
||||
assert any([not all(x) for x in mask_history])
|
||||
# When the agent takes action N from its action map, we need to have a look at the action mask and make sure that
|
||||
# the N-th entry was True, meaning that it was a valid action at that step.
|
||||
# This plucks out the mask history at step i, and at action entry a and checks that it's set to True, and this
|
||||
# happens for all steps i in the episode
|
||||
assert all(mask_history[i][a] for i, a in enumerate(action_num_history))
|
||||
monkeypatch.undo()
|
||||
|
||||
|
||||
def test_ray_single_agent_action_masking(monkeypatch):
|
||||
"""Check that a Ray agent uses the action mask and never chooses invalid actions."""
|
||||
with open(CFG_PATH, "r") as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
for agent in cfg["agents"]:
|
||||
if agent["ref"] == "defender":
|
||||
agent["agent_settings"]["flatten_obs"] = True
|
||||
|
||||
# There's no simple way of capturing what the action mask was at every step, therefore we are mocking the step
|
||||
# function to save the action mask and the agent's chosen action to a local variable.
|
||||
old_step_method = PrimaiteRayEnv.step
|
||||
action_num_history = []
|
||||
mask_history = []
|
||||
|
||||
def cache_step(self, action: int):
|
||||
action_num_history.append(action)
|
||||
obs, *_ = old_step_method(self, action)
|
||||
action_mask = obs["action_mask"]
|
||||
mask_history.append(action_mask)
|
||||
return obs, *_
|
||||
|
||||
monkeypatch.setattr(PrimaiteRayEnv, "step", lambda *args, **kwargs: cache_step(*args, **kwargs))
|
||||
|
||||
# Configure Ray PPO to use action masking by using the ActionMaskingTorchRLModule
|
||||
config = (
|
||||
PPOConfig()
|
||||
.api_stack(enable_rl_module_and_learner=True, enable_env_runner_and_connector_v2=True)
|
||||
.environment(env=PrimaiteRayEnv, env_config=cfg, action_mask_key="action_mask")
|
||||
.rl_module(rl_module_spec=SingleAgentRLModuleSpec(module_class=ActionMaskingTorchRLModule))
|
||||
.env_runners(num_env_runners=0)
|
||||
.training(train_batch_size=128)
|
||||
)
|
||||
algo = config.build()
|
||||
algo.train()
|
||||
|
||||
assert len(action_num_history) == len(mask_history) > 0
|
||||
# Make sure the masks had at least some False entries, if it was all True then the mask was disabled
|
||||
assert any([not all(x) for x in mask_history])
|
||||
# When the agent takes action N from its action map, we need to have a look at the action mask and make sure that
|
||||
# the N-th action was valid.
|
||||
# The first step uses the action mask provided by the reset method, so we are only checking from the second step
|
||||
# onward, that's why we need to use mask_history[:-1] and action_num_history[1:]
|
||||
assert all(mask_history[:-1][i][a] for i, a in enumerate(action_num_history[1:]))
|
||||
monkeypatch.undo()
|
||||
|
||||
|
||||
def test_ray_multi_agent_action_masking(monkeypatch):
|
||||
"""Check that Ray agents never take invalid actions when using MARL."""
|
||||
with open(MARL_PATH, "r") as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
|
||||
old_step_method = PrimaiteRayMARLEnv.step
|
||||
action_num_history = {"defender_1": [], "defender_2": []}
|
||||
mask_history = {"defender_1": [], "defender_2": []}
|
||||
|
||||
def cache_step(self, actions: Dict[str, int]):
|
||||
for agent_name, action in actions.items():
|
||||
action_num_history[agent_name].append(action)
|
||||
obs, *_ = old_step_method(self, actions)
|
||||
for (
|
||||
agent_name,
|
||||
o,
|
||||
) in obs.items():
|
||||
mask_history[agent_name].append(o["action_mask"])
|
||||
return obs, *_
|
||||
|
||||
monkeypatch.setattr(PrimaiteRayMARLEnv, "step", lambda *args, **kwargs: cache_step(*args, **kwargs))
|
||||
|
||||
config = (
|
||||
PPOConfig()
|
||||
.multi_agent(
|
||||
policies={
|
||||
"defender_1",
|
||||
"defender_2",
|
||||
}, # These names are the same as the agents defined in the example config.
|
||||
policy_mapping_fn=lambda agent_id, *args, **kwargs: agent_id,
|
||||
)
|
||||
.api_stack(enable_rl_module_and_learner=True, enable_env_runner_and_connector_v2=True)
|
||||
.environment(env=PrimaiteRayMARLEnv, env_config=cfg, action_mask_key="action_mask")
|
||||
.rl_module(
|
||||
rl_module_spec=MultiAgentRLModuleSpec(
|
||||
module_specs={
|
||||
"defender_1": SingleAgentRLModuleSpec(module_class=ActionMaskingTorchRLModule),
|
||||
"defender_2": SingleAgentRLModuleSpec(module_class=ActionMaskingTorchRLModule),
|
||||
}
|
||||
)
|
||||
)
|
||||
.env_runners(num_env_runners=0)
|
||||
.training(train_batch_size=128)
|
||||
)
|
||||
algo = config.build()
|
||||
algo.train()
|
||||
|
||||
for agent_name in ["defender_1", "defender_2"]:
|
||||
act_hist = action_num_history[agent_name]
|
||||
mask_hist = mask_history[agent_name]
|
||||
assert len(act_hist) == len(mask_hist) > 0
|
||||
assert any([not all(x) for x in mask_hist])
|
||||
assert all(mask_hist[:-1][i][a] for i, a in enumerate(act_hist[1:]))
|
||||
monkeypatch.undo()
|
||||
@@ -1,7 +1,5 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
import ray
|
||||
import yaml
|
||||
from ray import air, tune
|
||||
from ray.rllib.algorithms.ppo import PPOConfig
|
||||
|
||||
from primaite.session.ray_envs import PrimaiteRayMARLEnv
|
||||
@@ -12,12 +10,9 @@ MULTI_AGENT_PATH = TEST_ASSETS_ROOT / "configs/multi_agent_session.yaml"
|
||||
|
||||
def test_rllib_multi_agent_compatibility():
|
||||
"""Test that the PrimaiteRayEnv class can be used with a multi agent RLLIB system."""
|
||||
|
||||
with open(MULTI_AGENT_PATH, "r") as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
|
||||
ray.init()
|
||||
|
||||
config = (
|
||||
PPOConfig()
|
||||
.environment(env=PrimaiteRayMARLEnv, env_config=cfg)
|
||||
@@ -28,15 +23,5 @@ def test_rllib_multi_agent_compatibility():
|
||||
)
|
||||
.training(train_batch_size=128)
|
||||
)
|
||||
|
||||
tune.Tuner(
|
||||
"PPO",
|
||||
run_config=air.RunConfig(
|
||||
stop={"training_iteration": 128},
|
||||
checkpoint_config=air.CheckpointConfig(
|
||||
checkpoint_frequency=10,
|
||||
),
|
||||
),
|
||||
param_space=config,
|
||||
).fit()
|
||||
ray.shutdown()
|
||||
algo = config.build()
|
||||
algo.train()
|
||||
|
||||
@@ -3,7 +3,6 @@ import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import ray
|
||||
import yaml
|
||||
from ray.rllib.algorithms import ppo
|
||||
|
||||
@@ -20,9 +19,6 @@ def test_rllib_single_agent_compatibility():
|
||||
|
||||
game = PrimaiteGame.from_config(cfg)
|
||||
|
||||
ray.shutdown()
|
||||
ray.init()
|
||||
|
||||
env_config = {"game": game}
|
||||
config = {
|
||||
"env": PrimaiteRayEnv,
|
||||
@@ -41,4 +37,3 @@ def test_rllib_single_agent_compatibility():
|
||||
assert save_file.exists()
|
||||
|
||||
save_file.unlink() # clean up
|
||||
ray.shutdown()
|
||||
|
||||
@@ -20,7 +20,7 @@ def test_sb3_compatibility():
|
||||
gym = PrimaiteGymEnv(env_config=cfg)
|
||||
model = PPO("MlpPolicy", gym)
|
||||
|
||||
model.learn(total_timesteps=1000)
|
||||
model.learn(total_timesteps=256)
|
||||
|
||||
save_path = Path(tempfile.gettempdir()) / "model.zip"
|
||||
model.save(save_path)
|
||||
|
||||
@@ -65,25 +65,25 @@ class TestPrimaiteEnvironment:
|
||||
cfg = yaml.safe_load(f)
|
||||
env = PrimaiteRayMARLEnv(env_config=cfg)
|
||||
|
||||
assert set(env._agent_ids) == {"defender1", "defender2"}
|
||||
assert set(env._agent_ids) == {"defender_1", "defender_2"}
|
||||
|
||||
assert len(env.agents) == 2
|
||||
defender1 = env.agents["defender1"]
|
||||
defender2 = env.agents["defender2"]
|
||||
assert (num_actions_1 := len(defender1.action_manager.action_map)) == 54
|
||||
assert (num_actions_2 := len(defender2.action_manager.action_map)) == 38
|
||||
defender_1 = env.agents["defender_1"]
|
||||
defender_2 = env.agents["defender_2"]
|
||||
assert (num_actions_1 := len(defender_1.action_manager.action_map)) == 78
|
||||
assert (num_actions_2 := len(defender_2.action_manager.action_map)) == 78
|
||||
|
||||
# ensure we can run all valid actions without error
|
||||
for act_1 in range(num_actions_1):
|
||||
env.step({"defender1": act_1, "defender2": 0})
|
||||
env.step({"defender_1": act_1, "defender_2": 0})
|
||||
for act_2 in range(num_actions_2):
|
||||
env.step({"defender1": 0, "defender2": act_2})
|
||||
env.step({"defender_1": 0, "defender_2": act_2})
|
||||
|
||||
# ensure we get error when taking an invalid action
|
||||
with pytest.raises(KeyError):
|
||||
env.step({"defender1": num_actions_1, "defender2": 0})
|
||||
env.step({"defender_1": num_actions_1, "defender_2": 0})
|
||||
with pytest.raises(KeyError):
|
||||
env.step({"defender1": 0, "defender2": num_actions_2})
|
||||
env.step({"defender_1": 0, "defender_2": num_actions_2})
|
||||
|
||||
def test_error_thrown_on_bad_configuration(self):
|
||||
"""Make sure we throw an error when the config is bad."""
|
||||
|
||||
@@ -67,7 +67,7 @@ def test_dev_mode_config_sys_log_level():
|
||||
# check defaults
|
||||
assert PRIMAITE_CONFIG["developer_mode"]["sys_log_level"] == "DEBUG" # DEBUG by default
|
||||
|
||||
result = cli(["dev-mode", "config", "-level", "WARNING"])
|
||||
result = cli(["dev-mode", "config", "--sys-log-level", "WARNING"])
|
||||
|
||||
assert "sys_log_level=WARNING" in result.output # should print correct value
|
||||
|
||||
@@ -78,10 +78,30 @@ def test_dev_mode_config_sys_log_level():
|
||||
|
||||
assert "sys_log_level=INFO" in result.output # should print correct value
|
||||
|
||||
# config should reflect that log level is WARNING
|
||||
# config should reflect that log level is INFO
|
||||
assert PRIMAITE_CONFIG["developer_mode"]["sys_log_level"] == "INFO"
|
||||
|
||||
|
||||
def test_dev_mode_config_agent_log_level():
|
||||
"""Check that the agent log level can be changed via CLI."""
|
||||
# check defaults
|
||||
assert PRIMAITE_CONFIG["developer_mode"]["agent_log_level"] == "DEBUG" # DEBUG by default
|
||||
|
||||
result = cli(["dev-mode", "config", "-level", "WARNING"])
|
||||
|
||||
assert "agent_log_level=WARNING" in result.output # should print correct value
|
||||
|
||||
# config should reflect that log level is WARNING
|
||||
assert PRIMAITE_CONFIG["developer_mode"]["agent_log_level"] == "WARNING"
|
||||
|
||||
result = cli(["dev-mode", "config", "--agent-log-level", "INFO"])
|
||||
|
||||
assert "agent_log_level=INFO" in result.output # should print correct value
|
||||
|
||||
# config should reflect that log level is INFO
|
||||
assert PRIMAITE_CONFIG["developer_mode"]["agent_log_level"] == "INFO"
|
||||
|
||||
|
||||
def test_dev_mode_config_sys_logs_enable_disable():
|
||||
"""Test that the system logs output can be enabled or disabled."""
|
||||
# check defaults
|
||||
@@ -112,6 +132,36 @@ def test_dev_mode_config_sys_logs_enable_disable():
|
||||
assert PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] is False
|
||||
|
||||
|
||||
def test_dev_mode_config_agent_logs_enable_disable():
|
||||
"""Test that the agent logs output can be enabled or disabled."""
|
||||
# check defaults
|
||||
assert PRIMAITE_CONFIG["developer_mode"]["output_agent_logs"] is False # False by default
|
||||
|
||||
result = cli(["dev-mode", "config", "--output-agent-logs"])
|
||||
assert "output_agent_logs=True" in result.output # should print correct value
|
||||
|
||||
# config should reflect that output_agent_logs is True
|
||||
assert PRIMAITE_CONFIG["developer_mode"]["output_agent_logs"]
|
||||
|
||||
result = cli(["dev-mode", "config", "--no-agent-logs"])
|
||||
assert "output_agent_logs=False" in result.output # should print correct value
|
||||
|
||||
# config should reflect that output_agent_logs is True
|
||||
assert PRIMAITE_CONFIG["developer_mode"]["output_agent_logs"] is False
|
||||
|
||||
result = cli(["dev-mode", "config", "-agent"])
|
||||
assert "output_agent_logs=True" in result.output # should print correct value
|
||||
|
||||
# config should reflect that output_agent_logs is True
|
||||
assert PRIMAITE_CONFIG["developer_mode"]["output_agent_logs"]
|
||||
|
||||
result = cli(["dev-mode", "config", "-nagent"])
|
||||
assert "output_agent_logs=False" in result.output # should print correct value
|
||||
|
||||
# config should reflect that output_agent_logs is True
|
||||
assert PRIMAITE_CONFIG["developer_mode"]["output_agent_logs"] is False
|
||||
|
||||
|
||||
def test_dev_mode_config_pcap_logs_enable_disable():
|
||||
"""Test that the pcap logs output can be enabled or disabled."""
|
||||
# check defaults
|
||||
|
||||
@@ -35,3 +35,7 @@ def test_io_settings():
|
||||
assert env.io.settings.save_step_metadata is False
|
||||
|
||||
assert env.io.settings.write_sys_log_to_terminal is False # false by default
|
||||
|
||||
assert env.io.settings.save_agent_logs is True
|
||||
assert env.io.settings.agent_log_level is LogLevel.INFO
|
||||
assert env.io.settings.write_agent_log_to_terminal is True # Set to True by the config file.
|
||||
|
||||
@@ -1,35 +1,23 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
import copy
|
||||
from ipaddress import IPv4Address
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
import yaml
|
||||
|
||||
from primaite.config.load import data_manipulation_config_path
|
||||
from primaite.game.agent.interface import ProxyAgent
|
||||
from primaite.game.agent.scripted_agents.data_manipulation_bot import DataManipulationAgent
|
||||
from primaite.game.agent.scripted_agents.probabilistic_agent import ProbabilisticAgent
|
||||
from primaite.game.game import APPLICATION_TYPES_MAPPING, PrimaiteGame, SERVICE_TYPES_MAPPING
|
||||
from primaite.simulator.network.container import Network
|
||||
from primaite.game.game import PrimaiteGame, SERVICE_TYPES_MAPPING
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
from primaite.simulator.system.applications.application import Application
|
||||
from primaite.simulator.system.applications.database_client import DatabaseClient
|
||||
from primaite.simulator.system.applications.red_applications.data_manipulation_bot import DataManipulationBot
|
||||
from primaite.simulator.system.applications.red_applications.dos_bot import DoSBot
|
||||
from primaite.simulator.system.applications.web_browser import WebBrowser
|
||||
from primaite.simulator.system.services.database.database_service import DatabaseService
|
||||
from primaite.simulator.system.services.dns.dns_client import DNSClient
|
||||
from primaite.simulator.system.services.dns.dns_server import DNSServer
|
||||
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
||||
from primaite.simulator.system.services.ftp.ftp_server import FTPServer
|
||||
from primaite.simulator.system.services.ntp.ntp_client import NTPClient
|
||||
from primaite.simulator.system.services.ntp.ntp_server import NTPServer
|
||||
from primaite.simulator.system.services.web_server.web_server import WebServer
|
||||
from tests import TEST_ASSETS_ROOT
|
||||
|
||||
TEST_CONFIG = TEST_ASSETS_ROOT / "configs/software_fix_duration.yaml"
|
||||
ONE_ITEM_CONFIG = TEST_ASSETS_ROOT / "configs/fix_duration_one_item.yaml"
|
||||
|
||||
TestApplications = ["DummyApplication", "BroadcastTestClient"]
|
||||
|
||||
|
||||
def load_config(config_path: Union[str, Path]) -> PrimaiteGame:
|
||||
"""Returns a PrimaiteGame object which loads the contents of a given yaml path."""
|
||||
@@ -62,9 +50,12 @@ def test_fix_duration_set_from_config():
|
||||
assert client_1.software_manager.software.get(service).fixing_duration == 3
|
||||
|
||||
# in config - applications take 1 timestep to fix
|
||||
for applications in APPLICATION_TYPES_MAPPING:
|
||||
assert client_1.software_manager.software.get(applications) is not None
|
||||
assert client_1.software_manager.software.get(applications).fixing_duration == 1
|
||||
# remove test applications from list
|
||||
applications = set(Application._application_registry) - set(TestApplications)
|
||||
|
||||
for application in applications:
|
||||
assert client_1.software_manager.software.get(application) is not None
|
||||
assert client_1.software_manager.software.get(application).fixing_duration == 1
|
||||
|
||||
|
||||
def test_fix_duration_for_one_item():
|
||||
@@ -80,8 +71,9 @@ def test_fix_duration_for_one_item():
|
||||
assert client_1.software_manager.software.get(service).fixing_duration == 2
|
||||
|
||||
# in config - applications take 1 timestep to fix
|
||||
applications = copy.copy(APPLICATION_TYPES_MAPPING)
|
||||
applications.pop("DatabaseClient")
|
||||
# remove test applications from list
|
||||
applications = set(Application._application_registry) - set(TestApplications)
|
||||
applications.remove("DatabaseClient")
|
||||
for applications in applications:
|
||||
assert client_1.software_manager.software.get(applications) is not None
|
||||
assert client_1.software_manager.software.get(applications).fixing_duration == 2
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
from typing import Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from primaite.game.agent.interface import ProxyAgent
|
||||
from primaite.game.game import PrimaiteGame
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
from primaite.simulator.network.hardware.nodes.host.server import Server
|
||||
from primaite.simulator.system.applications.application import ApplicationOperatingState
|
||||
from primaite.simulator.system.applications.web_browser import WebBrowser
|
||||
from primaite.simulator.system.services.service import ServiceOperatingState
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def game_and_agent_fixture(game_and_agent):
|
||||
"""Create a game with a simple agent that can be controlled by the tests."""
|
||||
game, agent = game_and_agent
|
||||
|
||||
client_1: Computer = game.simulation.network.get_node_by_hostname("client_1")
|
||||
client_1.start_up_duration = 3
|
||||
|
||||
return (game, agent)
|
||||
|
||||
|
||||
def test_application_cannot_perform_actions_unless_running(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test the the request permissions prevent any actions unless application is running."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
browser: WebBrowser = client_1.software_manager.software.get("WebBrowser")
|
||||
|
||||
browser.close()
|
||||
assert browser.operating_state == ApplicationOperatingState.CLOSED
|
||||
|
||||
action = ("NODE_APPLICATION_SCAN", {"node_id": 0, "application_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert browser.operating_state == ApplicationOperatingState.CLOSED
|
||||
|
||||
action = ("NODE_APPLICATION_CLOSE", {"node_id": 0, "application_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert browser.operating_state == ApplicationOperatingState.CLOSED
|
||||
|
||||
action = ("NODE_APPLICATION_FIX", {"node_id": 0, "application_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert browser.operating_state == ApplicationOperatingState.CLOSED
|
||||
|
||||
action = ("NODE_APPLICATION_EXECUTE", {"node_id": 0, "application_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert browser.operating_state == ApplicationOperatingState.CLOSED
|
||||
@@ -99,7 +99,7 @@ class TestConfigureDatabaseAction:
|
||||
game.step()
|
||||
|
||||
assert db_client.server_ip_address == old_ip
|
||||
assert db_client.server_password is "admin123"
|
||||
assert db_client.server_password == "admin123"
|
||||
|
||||
|
||||
class TestConfigureRansomwareScriptAction:
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
import uuid
|
||||
from typing import Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from primaite.game.agent.interface import ProxyAgent
|
||||
from primaite.game.game import PrimaiteGame
|
||||
from primaite.simulator.file_system.file_system_item_abc import FileSystemItemHealthStatus
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def game_and_agent_fixture(game_and_agent):
|
||||
"""Create a game with a simple agent that can be controlled by the tests."""
|
||||
game, agent = game_and_agent
|
||||
|
||||
client_1: Computer = game.simulation.network.get_node_by_hostname("client_1")
|
||||
client_1.start_up_duration = 3
|
||||
|
||||
return (game, agent)
|
||||
|
||||
|
||||
def test_create_file(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test that the validator allows a files to be created."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
|
||||
random_folder = str(uuid.uuid4())
|
||||
random_file = str(uuid.uuid4())
|
||||
|
||||
assert client_1.file_system.get_file(folder_name=random_folder, file_name=random_file) is None
|
||||
|
||||
action = (
|
||||
"NODE_FILE_CREATE",
|
||||
{"node_id": 0, "folder_name": random_folder, "file_name": random_file},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert client_1.file_system.get_file(folder_name=random_folder, file_name=random_file) is not None
|
||||
|
||||
|
||||
def test_file_delete_action(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test that the validator allows a file to be deleted."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
file = client_1.file_system.get_file(folder_name="downloads", file_name="cat.png")
|
||||
assert file.deleted is False
|
||||
|
||||
action = (
|
||||
"NODE_FILE_DELETE",
|
||||
{"node_id": 0, "folder_id": 0, "file_id": 0},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert file.deleted
|
||||
|
||||
|
||||
def test_file_scan_action(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test that the validator allows a file to be scanned."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
file = client_1.file_system.get_file(folder_name="downloads", file_name="cat.png")
|
||||
|
||||
file.corrupt()
|
||||
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
assert file.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||
|
||||
action = (
|
||||
"NODE_FILE_SCAN",
|
||||
{"node_id": 0, "folder_id": 0, "file_id": 0},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
assert file.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
|
||||
|
||||
def test_file_repair_action(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test that the validator allows a folder to be created."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
file = client_1.file_system.get_file(folder_name="downloads", file_name="cat.png")
|
||||
|
||||
file.corrupt()
|
||||
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
|
||||
action = (
|
||||
"NODE_FILE_REPAIR",
|
||||
{"node_id": 0, "folder_id": 0, "file_id": 0},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert file.health_status == FileSystemItemHealthStatus.GOOD
|
||||
|
||||
|
||||
def test_file_restore_action(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test that the validator allows a file to be restored."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
file = client_1.file_system.get_file(folder_name="downloads", file_name="cat.png")
|
||||
|
||||
file.corrupt()
|
||||
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
|
||||
action = (
|
||||
"NODE_FILE_RESTORE",
|
||||
{"node_id": 0, "folder_id": 0, "file_id": 0},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert file.health_status == FileSystemItemHealthStatus.GOOD
|
||||
|
||||
|
||||
def test_file_corrupt_action(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test that the validator allows a file to be corrupted."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
file = client_1.file_system.get_file(folder_name="downloads", file_name="cat.png")
|
||||
|
||||
assert file.health_status == FileSystemItemHealthStatus.GOOD
|
||||
|
||||
action = (
|
||||
"NODE_FILE_CORRUPT",
|
||||
{"node_id": 0, "folder_id": 0, "file_id": 0},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
|
||||
|
||||
def test_file_access_action(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test that the validator allows a file to be accessed."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
file = client_1.file_system.get_file(folder_name="downloads", file_name="cat.png")
|
||||
assert file.num_access == 0
|
||||
|
||||
action = (
|
||||
"NODE_FILE_ACCESS",
|
||||
{"node_id": 0, "folder_name": file.folder_name, "file_name": file.name},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert file.num_access == 1
|
||||
@@ -0,0 +1,123 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
import uuid
|
||||
from typing import Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from primaite.game.agent.interface import ProxyAgent
|
||||
from primaite.game.game import PrimaiteGame
|
||||
from primaite.simulator.file_system.file_system_item_abc import FileSystemItemHealthStatus
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def game_and_agent_fixture(game_and_agent):
|
||||
"""Create a game with a simple agent that can be controlled by the tests."""
|
||||
game, agent = game_and_agent
|
||||
|
||||
client_1: Computer = game.simulation.network.get_node_by_hostname("client_1")
|
||||
client_1.start_up_duration = 3
|
||||
|
||||
return (game, agent)
|
||||
|
||||
|
||||
def test_create_folder(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test that the validator allows a folder to be created."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
|
||||
random_folder = str(uuid.uuid4())
|
||||
|
||||
assert client_1.file_system.get_folder(folder_name=random_folder) is None
|
||||
|
||||
action = (
|
||||
"NODE_FOLDER_CREATE",
|
||||
{
|
||||
"node_id": 0,
|
||||
"folder_name": random_folder,
|
||||
},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert client_1.file_system.get_folder(folder_name=random_folder) is not None
|
||||
|
||||
|
||||
def test_folder_scan_action(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test to make sure that the validator checks if the folder exists before scanning."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
|
||||
folder = client_1.file_system.get_folder(folder_name="downloads")
|
||||
assert folder.health_status == FileSystemItemHealthStatus.GOOD
|
||||
assert folder.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||
|
||||
folder.corrupt()
|
||||
|
||||
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
assert folder.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||
|
||||
action = (
|
||||
"NODE_FOLDER_SCAN",
|
||||
{
|
||||
"node_id": 0, # client_1,
|
||||
"folder_id": 0, # downloads
|
||||
},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
for i in range(folder.scan_duration + 1):
|
||||
game.step()
|
||||
|
||||
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
assert folder.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
|
||||
|
||||
def test_folder_repair_action(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test to make sure that the validator checks if the folder exists before repairing."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
|
||||
folder = client_1.file_system.get_folder(folder_name="downloads")
|
||||
folder.corrupt()
|
||||
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
|
||||
action = (
|
||||
"NODE_FOLDER_REPAIR",
|
||||
{
|
||||
"node_id": 0, # client_1,
|
||||
"folder_id": 0, # downloads
|
||||
},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert folder.health_status == FileSystemItemHealthStatus.GOOD
|
||||
|
||||
|
||||
def test_folder_restore_action(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test to make sure that the validator checks if the folder exists before restoring."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
|
||||
folder = client_1.file_system.get_folder(folder_name="downloads")
|
||||
folder.corrupt()
|
||||
|
||||
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
|
||||
action = (
|
||||
"NODE_FOLDER_RESTORE",
|
||||
{
|
||||
"node_id": 0, # client_1,
|
||||
"folder_id": 0, # downloads
|
||||
},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert folder.health_status == FileSystemItemHealthStatus.RESTORING
|
||||
@@ -0,0 +1,95 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
from typing import Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from primaite.game.agent.interface import ProxyAgent
|
||||
from primaite.game.game import PrimaiteGame
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def game_and_agent_fixture(game_and_agent):
|
||||
"""Create a game with a simple agent that can be controlled by the tests."""
|
||||
game, agent = game_and_agent
|
||||
|
||||
client_1: Computer = game.simulation.network.get_node_by_hostname("client_1")
|
||||
client_1.start_up_duration = 3
|
||||
|
||||
return (game, agent)
|
||||
|
||||
|
||||
def test_nic_cannot_be_turned_off_if_not_on(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test that a NIC cannot be disabled if it is not enabled."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
nic = client_1.network_interface[1]
|
||||
nic.disable()
|
||||
assert nic.enabled is False
|
||||
|
||||
action = (
|
||||
"HOST_NIC_DISABLE",
|
||||
{
|
||||
"node_id": 0, # client_1
|
||||
"nic_id": 0, # the only nic (eth-1)
|
||||
},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert nic.enabled is False
|
||||
|
||||
|
||||
def test_nic_cannot_be_turned_on_if_already_on(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test that a NIC cannot be enabled if it is already enabled."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
nic = client_1.network_interface[1]
|
||||
assert nic.enabled
|
||||
|
||||
action = (
|
||||
"HOST_NIC_ENABLE",
|
||||
{
|
||||
"node_id": 0, # client_1
|
||||
"nic_id": 0, # the only nic (eth-1)
|
||||
},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert nic.enabled
|
||||
|
||||
|
||||
def test_that_a_nic_can_be_enabled_and_disabled(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Tests that a NIC can be enabled and disabled."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
nic = client_1.network_interface[1]
|
||||
assert nic.enabled
|
||||
|
||||
action = (
|
||||
"HOST_NIC_DISABLE",
|
||||
{
|
||||
"node_id": 0, # client_1
|
||||
"nic_id": 0, # the only nic (eth-1)
|
||||
},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert nic.enabled is False
|
||||
|
||||
action = (
|
||||
"HOST_NIC_ENABLE",
|
||||
{
|
||||
"node_id": 0, # client_1
|
||||
"nic_id": 0, # the only nic (eth-1)
|
||||
},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert nic.enabled
|
||||
@@ -0,0 +1,94 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
from typing import Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from primaite.game.agent.interface import ProxyAgent
|
||||
from primaite.game.game import PrimaiteGame
|
||||
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def game_and_agent_fixture(game_and_agent):
|
||||
"""Create a game with a simple agent that can be controlled by the tests."""
|
||||
game, agent = game_and_agent
|
||||
|
||||
client_1: Computer = game.simulation.network.get_node_by_hostname("client_1")
|
||||
client_1.start_up_duration = 3
|
||||
|
||||
return (game, agent)
|
||||
|
||||
|
||||
def test_node_startup_shutdown(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test that the node can be shut down and started up."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
|
||||
assert client_1.operating_state == NodeOperatingState.ON
|
||||
|
||||
# turn it off
|
||||
action = ("NODE_SHUTDOWN", {"node_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert client_1.operating_state == NodeOperatingState.SHUTTING_DOWN
|
||||
|
||||
for i in range(client_1.shut_down_duration + 1):
|
||||
action = ("DONOTHING", {"node_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert client_1.operating_state == NodeOperatingState.OFF
|
||||
|
||||
# turn it on
|
||||
action = ("NODE_STARTUP", {"node_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert client_1.operating_state == NodeOperatingState.BOOTING
|
||||
|
||||
for i in range(client_1.start_up_duration + 1):
|
||||
action = ("DONOTHING", {"node_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert client_1.operating_state == NodeOperatingState.ON
|
||||
|
||||
|
||||
def test_node_cannot_be_started_up_if_node_is_already_on(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test that a node cannot be started up if it is already on."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
assert client_1.operating_state == NodeOperatingState.ON
|
||||
|
||||
# turn it on
|
||||
action = ("NODE_STARTUP", {"node_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert client_1.operating_state == NodeOperatingState.ON
|
||||
|
||||
|
||||
def test_node_cannot_be_shut_down_if_node_is_already_off(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test that a node cannot be shut down if it is already off."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
client_1.power_off()
|
||||
|
||||
for i in range(client_1.shut_down_duration + 1):
|
||||
action = ("DONOTHING", {"node_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert client_1.operating_state == NodeOperatingState.OFF
|
||||
|
||||
# turn it ff
|
||||
action = ("NODE_SHUTDOWN", {"node_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert client_1.operating_state == NodeOperatingState.OFF
|
||||
@@ -0,0 +1,106 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
from typing import Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from primaite.game.agent.interface import ProxyAgent
|
||||
from primaite.game.game import PrimaiteGame
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
from primaite.simulator.network.hardware.nodes.host.server import Server
|
||||
from primaite.simulator.system.services.service import ServiceOperatingState
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def game_and_agent_fixture(game_and_agent):
|
||||
"""Create a game with a simple agent that can be controlled by the tests."""
|
||||
game, agent = game_and_agent
|
||||
|
||||
client_1: Computer = game.simulation.network.get_node_by_hostname("client_1")
|
||||
client_1.start_up_duration = 3
|
||||
|
||||
return (game, agent)
|
||||
|
||||
|
||||
def test_service_start(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test that the validator makes sure that the service is stopped before starting the service."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
server_1: Server = game.simulation.network.get_node_by_hostname("server_1")
|
||||
dns_server = server_1.software_manager.software.get("DNSServer")
|
||||
|
||||
dns_server.pause()
|
||||
assert dns_server.operating_state == ServiceOperatingState.PAUSED
|
||||
|
||||
action = ("NODE_SERVICE_START", {"node_id": 1, "service_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert dns_server.operating_state == ServiceOperatingState.PAUSED
|
||||
|
||||
dns_server.stop()
|
||||
|
||||
assert dns_server.operating_state == ServiceOperatingState.STOPPED
|
||||
|
||||
action = ("NODE_SERVICE_START", {"node_id": 1, "service_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert dns_server.operating_state == ServiceOperatingState.RUNNING
|
||||
|
||||
|
||||
def test_service_resume(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test that the validator checks if the service is paused before resuming."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
server_1: Server = game.simulation.network.get_node_by_hostname("server_1")
|
||||
dns_server = server_1.software_manager.software.get("DNSServer")
|
||||
|
||||
action = ("NODE_SERVICE_RESUME", {"node_id": 1, "service_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert dns_server.operating_state == ServiceOperatingState.RUNNING
|
||||
|
||||
dns_server.pause()
|
||||
|
||||
assert dns_server.operating_state == ServiceOperatingState.PAUSED
|
||||
|
||||
action = ("NODE_SERVICE_RESUME", {"node_id": 1, "service_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert dns_server.operating_state == ServiceOperatingState.RUNNING
|
||||
|
||||
|
||||
def test_service_cannot_perform_actions_unless_running(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Test to make sure that the service cannot perform certain actions while not running."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
server_1: Server = game.simulation.network.get_node_by_hostname("server_1")
|
||||
dns_server = server_1.software_manager.software.get("DNSServer")
|
||||
|
||||
dns_server.stop()
|
||||
assert dns_server.operating_state == ServiceOperatingState.STOPPED
|
||||
|
||||
action = ("NODE_SERVICE_SCAN", {"node_id": 1, "service_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert dns_server.operating_state == ServiceOperatingState.STOPPED
|
||||
|
||||
action = ("NODE_SERVICE_PAUSE", {"node_id": 1, "service_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert dns_server.operating_state == ServiceOperatingState.STOPPED
|
||||
|
||||
action = ("NODE_SERVICE_RESUME", {"node_id": 1, "service_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert dns_server.operating_state == ServiceOperatingState.STOPPED
|
||||
|
||||
action = ("NODE_SERVICE_RESTART", {"node_id": 1, "service_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert dns_server.operating_state == ServiceOperatingState.STOPPED
|
||||
|
||||
action = ("NODE_SERVICE_FIX", {"node_id": 1, "service_id": 0})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert dns_server.operating_state == ServiceOperatingState.STOPPED
|
||||
@@ -155,7 +155,7 @@ def test_nic_monitored_traffic(simulation):
|
||||
assert traffic_obs["icmp"]["outbound"] == 0
|
||||
|
||||
# send a ping
|
||||
pc.ping(target_ip_address=pc2.network_interface[1].ip_address)
|
||||
assert pc.ping(target_ip_address=pc2.network_interface[1].ip_address)
|
||||
traffic_obs = nic_obs.observe(simulation.describe_state()).get("TRAFFIC")
|
||||
|
||||
assert traffic_obs["icmp"]["inbound"] == 1
|
||||
@@ -178,7 +178,7 @@ def test_nic_monitored_traffic(simulation):
|
||||
traffic_obs = nic_obs.observe(simulation.describe_state()).get("TRAFFIC")
|
||||
assert traffic_obs["icmp"]["inbound"] == 0
|
||||
assert traffic_obs["icmp"]["outbound"] == 0
|
||||
assert traffic_obs["tcp"][53]["inbound"] == 0
|
||||
assert traffic_obs["tcp"][53]["inbound"] == 1
|
||||
assert traffic_obs["tcp"][53]["outbound"] == 1 # getting a webpage sent a dns request out
|
||||
|
||||
simulation.pre_timestep(2) # apply timestep to whole sim
|
||||
|
||||
161
tests/integration_tests/game_layer/test_action_mask.py
Normal file
161
tests/integration_tests/game_layer/test_action_mask.py
Normal file
@@ -0,0 +1,161 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
from primaite.session.environment import PrimaiteGymEnv
|
||||
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||
from primaite.simulator.network.hardware.nodes.host.host_node import HostNode
|
||||
from primaite.simulator.system.services.service import ServiceOperatingState
|
||||
from tests.conftest import TEST_ASSETS_ROOT
|
||||
|
||||
CFG_PATH = TEST_ASSETS_ROOT / "configs/test_primaite_session.yaml"
|
||||
|
||||
|
||||
def test_mask_contents_correct():
|
||||
env = PrimaiteGymEnv(CFG_PATH)
|
||||
game = env.game
|
||||
sim = game.simulation
|
||||
net = sim.network
|
||||
mask = game.action_mask("defender")
|
||||
agent = env.agent
|
||||
node_list = agent.action_manager.node_names
|
||||
action_map = agent.action_manager.action_map
|
||||
|
||||
# CHECK NIC ENABLE/DISABLE ACTIONS
|
||||
for action_num, action in action_map.items():
|
||||
mask = game.action_mask("defender")
|
||||
act_type, act_params = action
|
||||
|
||||
if act_type == "NODE_NIC_ENABLE":
|
||||
node_name = node_list[act_params["node_id"]]
|
||||
node_obj = net.get_node_by_hostname(node_name)
|
||||
nic_obj = node_obj.network_interface[act_params["nic_id"] + 1]
|
||||
assert nic_obj.enabled
|
||||
assert not mask[action_num]
|
||||
nic_obj.disable()
|
||||
mask = game.action_mask("defender")
|
||||
assert mask[action_num]
|
||||
nic_obj.enable()
|
||||
|
||||
if act_type == "NODE_NIC_DISABLE":
|
||||
node_name = node_list[act_params["node_id"]]
|
||||
node_obj = net.get_node_by_hostname(node_name)
|
||||
nic_obj = node_obj.network_interface[act_params["nic_id"] + 1]
|
||||
assert nic_obj.enabled
|
||||
assert mask[action_num]
|
||||
nic_obj.disable()
|
||||
mask = game.action_mask("defender")
|
||||
assert not mask[action_num]
|
||||
nic_obj.enable()
|
||||
|
||||
if act_type == "ROUTER_ACL_ADDRULE":
|
||||
assert mask[action_num]
|
||||
|
||||
if act_type == "ROUTER_ACL_REMOVERULE":
|
||||
assert mask[action_num]
|
||||
|
||||
if act_type == "NODE_RESET":
|
||||
node_name = node_list[act_params["node_id"]]
|
||||
node_obj = net.get_node_by_hostname(node_name)
|
||||
assert node_obj.operating_state is NodeOperatingState.ON
|
||||
assert mask[action_num]
|
||||
node_obj.operating_state = NodeOperatingState.OFF
|
||||
mask = game.action_mask("defender")
|
||||
assert not mask[action_num]
|
||||
node_obj.operating_state = NodeOperatingState.ON
|
||||
|
||||
if act_type == "NODE_SHUTDOWN":
|
||||
node_name = node_list[act_params["node_id"]]
|
||||
node_obj = net.get_node_by_hostname(node_name)
|
||||
assert node_obj.operating_state is NodeOperatingState.ON
|
||||
assert mask[action_num]
|
||||
node_obj.operating_state = NodeOperatingState.OFF
|
||||
mask = game.action_mask("defender")
|
||||
assert not mask[action_num]
|
||||
node_obj.operating_state = NodeOperatingState.ON
|
||||
|
||||
if act_type == "NODE_OS_SCAN":
|
||||
node_name = node_list[act_params["node_id"]]
|
||||
node_obj = net.get_node_by_hostname(node_name)
|
||||
assert node_obj.operating_state is NodeOperatingState.ON
|
||||
assert mask[action_num]
|
||||
node_obj.operating_state = NodeOperatingState.OFF
|
||||
mask = game.action_mask("defender")
|
||||
assert not mask[action_num]
|
||||
node_obj.operating_state = NodeOperatingState.ON
|
||||
|
||||
if act_type == "NODE_STARTUP":
|
||||
node_name = node_list[act_params["node_id"]]
|
||||
node_obj = net.get_node_by_hostname(node_name)
|
||||
assert node_obj.operating_state is NodeOperatingState.ON
|
||||
assert not mask[action_num]
|
||||
node_obj.operating_state = NodeOperatingState.OFF
|
||||
mask = game.action_mask("defender")
|
||||
assert mask[action_num]
|
||||
node_obj.operating_state = NodeOperatingState.ON
|
||||
|
||||
if act_type == "DONOTHING":
|
||||
assert mask[action_num]
|
||||
|
||||
if act_type == "NODE_SERVICE_DISABLE":
|
||||
assert mask[action_num]
|
||||
|
||||
if act_type in ["NODE_SERVICE_SCAN", "NODE_SERVICE_STOP", "NODE_SERVICE_PAUSE"]:
|
||||
node_name = node_list[act_params["node_id"]]
|
||||
service_name = agent.action_manager.service_names[act_params["node_id"]][act_params["service_id"]]
|
||||
node_obj = net.get_node_by_hostname(node_name)
|
||||
service_obj = node_obj.software_manager.software.get(service_name)
|
||||
assert service_obj.operating_state is ServiceOperatingState.RUNNING
|
||||
assert mask[action_num]
|
||||
service_obj.operating_state = ServiceOperatingState.DISABLED
|
||||
mask = game.action_mask("defender")
|
||||
assert not mask[action_num]
|
||||
service_obj.operating_state = ServiceOperatingState.RUNNING
|
||||
|
||||
if act_type == "NODE_SERVICE_RESUME":
|
||||
node_name = node_list[act_params["node_id"]]
|
||||
service_name = agent.action_manager.service_names[act_params["node_id"]][act_params["service_id"]]
|
||||
node_obj = net.get_node_by_hostname(node_name)
|
||||
service_obj = node_obj.software_manager.software.get(service_name)
|
||||
assert service_obj.operating_state is ServiceOperatingState.RUNNING
|
||||
assert not mask[action_num]
|
||||
service_obj.operating_state = ServiceOperatingState.PAUSED
|
||||
mask = game.action_mask("defender")
|
||||
assert mask[action_num]
|
||||
service_obj.operating_state = ServiceOperatingState.RUNNING
|
||||
|
||||
if act_type == "NODE_SERVICE_START":
|
||||
node_name = node_list[act_params["node_id"]]
|
||||
service_name = agent.action_manager.service_names[act_params["node_id"]][act_params["service_id"]]
|
||||
node_obj = net.get_node_by_hostname(node_name)
|
||||
service_obj = node_obj.software_manager.software.get(service_name)
|
||||
assert service_obj.operating_state is ServiceOperatingState.RUNNING
|
||||
assert not mask[action_num]
|
||||
service_obj.operating_state = ServiceOperatingState.STOPPED
|
||||
mask = game.action_mask("defender")
|
||||
assert mask[action_num]
|
||||
service_obj.operating_state = ServiceOperatingState.RUNNING
|
||||
|
||||
if act_type == "NODE_SERVICE_ENABLE":
|
||||
node_name = node_list[act_params["node_id"]]
|
||||
service_name = agent.action_manager.service_names[act_params["node_id"]][act_params["service_id"]]
|
||||
node_obj = net.get_node_by_hostname(node_name)
|
||||
service_obj = node_obj.software_manager.software.get(service_name)
|
||||
assert service_obj.operating_state is ServiceOperatingState.RUNNING
|
||||
assert not mask[action_num]
|
||||
service_obj.operating_state = ServiceOperatingState.DISABLED
|
||||
mask = game.action_mask("defender")
|
||||
assert mask[action_num]
|
||||
service_obj.operating_state = ServiceOperatingState.RUNNING
|
||||
|
||||
if act_type in ["NODE_FILE_SCAN", "NODE_FILE_CHECKHASH", "NODE_FILE_DELETE"]:
|
||||
node_name = node_list[act_params["node_id"]]
|
||||
folder_name = agent.action_manager.get_folder_name_by_idx(act_params["node_id"], act_params["folder_id"])
|
||||
file_name = agent.action_manager.get_file_name_by_idx(
|
||||
act_params["node_id"], act_params["folder_id"], act_params["file_id"]
|
||||
)
|
||||
node_obj = net.get_node_by_hostname(node_name)
|
||||
file_obj = node_obj.file_system.get_file(folder_name, file_name, include_deleted=True)
|
||||
assert not file_obj.deleted
|
||||
assert mask[action_num]
|
||||
service_obj.operating_state = ServiceOperatingState.DISABLED
|
||||
mask = game.action_mask("defender")
|
||||
assert mask[action_num]
|
||||
service_obj.operating_state = ServiceOperatingState.RUNNING
|
||||
44
tests/integration_tests/network/test_airspace_config.py
Normal file
44
tests/integration_tests/network/test_airspace_config.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
import yaml
|
||||
|
||||
from primaite.game.game import PrimaiteGame
|
||||
from primaite.simulator.network.airspace import AirSpaceFrequency
|
||||
from tests import TEST_ASSETS_ROOT
|
||||
|
||||
|
||||
def test_override_freq_max_capacity_mbps():
|
||||
config_path = TEST_ASSETS_ROOT / "configs" / "wireless_wan_network_config_freq_max_override.yaml"
|
||||
|
||||
with open(config_path, "r") as f:
|
||||
config_dict = yaml.safe_load(f)
|
||||
network = PrimaiteGame.from_config(cfg=config_dict).simulation.network
|
||||
|
||||
assert network.airspace.get_frequency_max_capacity_mbps(AirSpaceFrequency.WIFI_2_4) == 123.45
|
||||
assert network.airspace.get_frequency_max_capacity_mbps(AirSpaceFrequency.WIFI_5) == 0.0
|
||||
|
||||
pc_a = network.get_node_by_hostname("pc_a")
|
||||
pc_b = network.get_node_by_hostname("pc_b")
|
||||
|
||||
assert pc_a.ping(pc_b.network_interface[1].ip_address), "PC A should be able to ping PC B"
|
||||
assert pc_b.ping(pc_a.network_interface[1].ip_address), "PC B should be able to ping PC A"
|
||||
|
||||
network.airspace.show()
|
||||
|
||||
|
||||
def test_override_freq_max_capacity_mbps_blocked():
|
||||
config_path = TEST_ASSETS_ROOT / "configs" / "wireless_wan_network_config_freq_max_override_blocked.yaml"
|
||||
|
||||
with open(config_path, "r") as f:
|
||||
config_dict = yaml.safe_load(f)
|
||||
network = PrimaiteGame.from_config(cfg=config_dict).simulation.network
|
||||
|
||||
assert network.airspace.get_frequency_max_capacity_mbps(AirSpaceFrequency.WIFI_2_4) == 0.0
|
||||
assert network.airspace.get_frequency_max_capacity_mbps(AirSpaceFrequency.WIFI_5) == 0.0
|
||||
|
||||
pc_a = network.get_node_by_hostname("pc_a")
|
||||
pc_b = network.get_node_by_hostname("pc_b")
|
||||
|
||||
assert not pc_a.ping(pc_b.network_interface[1].ip_address), "PC A should not be able to ping PC B"
|
||||
assert not pc_b.ping(pc_a.network_interface[1].ip_address), "PC B should not be able to ping PC A"
|
||||
|
||||
network.airspace.show()
|
||||
@@ -0,0 +1,114 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
from primaite.simulator.file_system.file_type import FileType
|
||||
from primaite.simulator.network.hardware.nodes.network.router import ACLAction
|
||||
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
||||
from primaite.simulator.system.services.ftp.ftp_server import FTPServer
|
||||
from tests.integration_tests.network.test_wireless_router import wireless_wan_network
|
||||
from tests.integration_tests.system.test_ftp_client_server import ftp_client_and_ftp_server
|
||||
|
||||
|
||||
def test_wireless_link_loading(wireless_wan_network):
|
||||
client, server, router_1, router_2 = wireless_wan_network
|
||||
|
||||
# Configure Router 1 ACLs
|
||||
router_1.acl.add_rule(action=ACLAction.PERMIT, position=1)
|
||||
|
||||
# Configure Router 2 ACLs
|
||||
router_2.acl.add_rule(action=ACLAction.PERMIT, position=1)
|
||||
|
||||
airspace = router_1.airspace
|
||||
|
||||
client.software_manager.install(FTPClient)
|
||||
ftp_client: FTPClient = client.software_manager.software.get("FTPClient")
|
||||
ftp_client.start()
|
||||
|
||||
server.software_manager.install(FTPServer)
|
||||
ftp_server: FTPServer = server.software_manager.software.get("FTPServer")
|
||||
ftp_server.start()
|
||||
|
||||
client.file_system.create_file(file_name="mixtape", size=10 * 10**6, file_type=FileType.MP3, folder_name="music")
|
||||
|
||||
assert ftp_client.send_file(
|
||||
src_file_name="mixtape.mp3",
|
||||
src_folder_name="music",
|
||||
dest_ip_address=server.network_interface[1].ip_address,
|
||||
dest_file_name="mixtape.mp3",
|
||||
dest_folder_name="music",
|
||||
)
|
||||
|
||||
# Reset the physical links between the host nodes and the routers
|
||||
client.network_interface[1]._connected_link.pre_timestep(1)
|
||||
server.network_interface[1]._connected_link.pre_timestep(1)
|
||||
|
||||
assert not ftp_client.send_file(
|
||||
src_file_name="mixtape.mp3",
|
||||
src_folder_name="music",
|
||||
dest_ip_address=server.network_interface[1].ip_address,
|
||||
dest_file_name="mixtape3.mp3",
|
||||
dest_folder_name="music",
|
||||
)
|
||||
|
||||
# Reset the physical links between the host nodes and the routers
|
||||
client.network_interface[1]._connected_link.pre_timestep(1)
|
||||
server.network_interface[1]._connected_link.pre_timestep(1)
|
||||
|
||||
airspace.reset_bandwidth_load()
|
||||
|
||||
assert ftp_client.send_file(
|
||||
src_file_name="mixtape.mp3",
|
||||
src_folder_name="music",
|
||||
dest_ip_address=server.network_interface[1].ip_address,
|
||||
dest_file_name="mixtape3.mp3",
|
||||
dest_folder_name="music",
|
||||
)
|
||||
|
||||
|
||||
def test_wired_link_loading(ftp_client_and_ftp_server):
|
||||
ftp_client, computer, ftp_server, server = ftp_client_and_ftp_server
|
||||
|
||||
link = computer.network_interface[1]._connected_link # noqa
|
||||
|
||||
assert link.is_up
|
||||
|
||||
link.pre_timestep(1)
|
||||
|
||||
computer.file_system.create_file(
|
||||
file_name="mixtape", size=10 * 10**6, file_type=FileType.MP3, folder_name="music"
|
||||
)
|
||||
link_load = link.current_load
|
||||
assert link_load == 0.0
|
||||
|
||||
assert ftp_client.send_file(
|
||||
src_file_name="mixtape.mp3",
|
||||
src_folder_name="music",
|
||||
dest_ip_address=server.network_interface[1].ip_address,
|
||||
dest_file_name="mixtape.mp3",
|
||||
dest_folder_name="music",
|
||||
)
|
||||
|
||||
new_link_load = link.current_load
|
||||
assert new_link_load > link_load
|
||||
|
||||
assert not ftp_client.send_file(
|
||||
src_file_name="mixtape.mp3",
|
||||
src_folder_name="music",
|
||||
dest_ip_address=server.network_interface[1].ip_address,
|
||||
dest_file_name="mixtape1.mp3",
|
||||
dest_folder_name="music",
|
||||
)
|
||||
|
||||
link.pre_timestep(2)
|
||||
|
||||
link_load = link.current_load
|
||||
assert link_load == 0.0
|
||||
|
||||
assert ftp_client.send_file(
|
||||
src_file_name="mixtape.mp3",
|
||||
src_folder_name="music",
|
||||
dest_ip_address=server.network_interface[1].ip_address,
|
||||
dest_file_name="mixtape1.mp3",
|
||||
dest_folder_name="music",
|
||||
)
|
||||
|
||||
new_link_load = link.current_load
|
||||
assert new_link_load > link_load
|
||||
@@ -14,7 +14,7 @@ from primaite.simulator.system.applications.application import Application
|
||||
from primaite.simulator.system.services.service import Service
|
||||
|
||||
|
||||
class BroadcastService(Service):
|
||||
class BroadcastTestService(Service):
|
||||
"""A service for sending broadcast and unicast messages over a network."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@@ -41,14 +41,14 @@ class BroadcastService(Service):
|
||||
super().send(payload="broadcast", dest_ip_address=ip_network, dest_port=Port.HTTP, ip_protocol=self.protocol)
|
||||
|
||||
|
||||
class BroadcastClient(Application, identifier="BroadcastClient"):
|
||||
class BroadcastTestClient(Application, identifier="BroadcastTestClient"):
|
||||
"""A client application to receive broadcast and unicast messages."""
|
||||
|
||||
payloads_received: List = []
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# Set default client properties
|
||||
kwargs["name"] = "BroadcastClient"
|
||||
kwargs["name"] = "BroadcastTestClient"
|
||||
kwargs["port"] = Port.HTTP
|
||||
kwargs["protocol"] = IPProtocol.TCP
|
||||
super().__init__(**kwargs)
|
||||
@@ -75,8 +75,8 @@ def broadcast_network() -> Network:
|
||||
start_up_duration=0,
|
||||
)
|
||||
client_1.power_on()
|
||||
client_1.software_manager.install(BroadcastClient)
|
||||
application_1 = client_1.software_manager.software["BroadcastClient"]
|
||||
client_1.software_manager.install(BroadcastTestClient)
|
||||
application_1 = client_1.software_manager.software["BroadcastTestClient"]
|
||||
application_1.run()
|
||||
|
||||
client_2 = Computer(
|
||||
@@ -87,8 +87,8 @@ def broadcast_network() -> Network:
|
||||
start_up_duration=0,
|
||||
)
|
||||
client_2.power_on()
|
||||
client_2.software_manager.install(BroadcastClient)
|
||||
application_2 = client_2.software_manager.software["BroadcastClient"]
|
||||
client_2.software_manager.install(BroadcastTestClient)
|
||||
application_2 = client_2.software_manager.software["BroadcastTestClient"]
|
||||
application_2.run()
|
||||
|
||||
server_1 = Server(
|
||||
@@ -100,8 +100,8 @@ def broadcast_network() -> Network:
|
||||
)
|
||||
server_1.power_on()
|
||||
|
||||
server_1.software_manager.install(BroadcastService)
|
||||
service: BroadcastService = server_1.software_manager.software["BroadcastService"]
|
||||
server_1.software_manager.install(BroadcastTestService)
|
||||
service: BroadcastTestService = server_1.software_manager.software["BroadcastService"]
|
||||
service.start()
|
||||
|
||||
switch_1 = Switch(hostname="switch_1", num_ports=6, start_up_duration=0)
|
||||
@@ -115,14 +115,16 @@ def broadcast_network() -> Network:
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def broadcast_service_and_clients(broadcast_network) -> Tuple[BroadcastService, BroadcastClient, BroadcastClient]:
|
||||
client_1: BroadcastClient = broadcast_network.get_node_by_hostname("client_1").software_manager.software[
|
||||
"BroadcastClient"
|
||||
def broadcast_service_and_clients(
|
||||
broadcast_network,
|
||||
) -> Tuple[BroadcastTestService, BroadcastTestClient, BroadcastTestClient]:
|
||||
client_1: BroadcastTestClient = broadcast_network.get_node_by_hostname("client_1").software_manager.software[
|
||||
"BroadcastTestClient"
|
||||
]
|
||||
client_2: BroadcastClient = broadcast_network.get_node_by_hostname("client_2").software_manager.software[
|
||||
"BroadcastClient"
|
||||
client_2: BroadcastTestClient = broadcast_network.get_node_by_hostname("client_2").software_manager.software[
|
||||
"BroadcastTestClient"
|
||||
]
|
||||
service: BroadcastService = broadcast_network.get_node_by_hostname("server_1").software_manager.software[
|
||||
service: BroadcastTestService = broadcast_network.get_node_by_hostname("server_1").software_manager.software[
|
||||
"BroadcastService"
|
||||
]
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ def test_port_scan_full_subnet_all_ports_and_protocols(example_network):
|
||||
|
||||
actual_result = client_1_nmap.port_scan(
|
||||
target_ip_address=IPv4Network("192.168.10.0/24"),
|
||||
target_port=[Port.ARP, Port.HTTP, Port.FTP, Port.DNS, Port.NTP],
|
||||
)
|
||||
|
||||
expected_result = {
|
||||
|
||||
137
tests/unit_tests/_primaite/_game/_agent/test_agent_log.py
Normal file
137
tests/unit_tests/_primaite/_game/_agent/test_agent_log.py
Normal file
@@ -0,0 +1,137 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from primaite import PRIMAITE_CONFIG
|
||||
from primaite.game.agent.agent_log import AgentLog
|
||||
from primaite.simulator import LogLevel, SIM_OUTPUT
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def override_dev_mode_temporarily():
|
||||
"""Temporarily turn off dev mode for this test."""
|
||||
primaite_dev_mode = PRIMAITE_CONFIG["developer_mode"]["enabled"]
|
||||
PRIMAITE_CONFIG["developer_mode"]["enabled"] = False
|
||||
yield # run tests
|
||||
PRIMAITE_CONFIG["developer_mode"]["enabled"] = primaite_dev_mode
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def agentlog() -> AgentLog:
|
||||
return AgentLog(agent_name="test_agent")
|
||||
|
||||
|
||||
def test_debug_agent_log_level(agentlog, capsys):
|
||||
"""Test that the debug log level logs debug agent logs and above."""
|
||||
SIM_OUTPUT.agent_log_level = LogLevel.DEBUG
|
||||
SIM_OUTPUT.write_agent_log_to_terminal = True
|
||||
|
||||
test_string = str(uuid4())
|
||||
|
||||
agentlog.debug(msg=test_string)
|
||||
agentlog.info(msg=test_string)
|
||||
agentlog.warning(msg=test_string)
|
||||
agentlog.error(msg=test_string)
|
||||
agentlog.critical(msg=test_string)
|
||||
|
||||
captured = "".join(capsys.readouterr())
|
||||
|
||||
assert test_string in captured
|
||||
assert "DEBUG" in captured
|
||||
assert "INFO" in captured
|
||||
assert "WARNING" in captured
|
||||
assert "ERROR" in captured
|
||||
assert "CRITICAL" in captured
|
||||
|
||||
|
||||
def test_info_agent_log_level(agentlog, capsys):
|
||||
"""Test that the debug log level logs debug agent logs and above."""
|
||||
SIM_OUTPUT.agent_log_level = LogLevel.INFO
|
||||
SIM_OUTPUT.write_agent_log_to_terminal = True
|
||||
|
||||
test_string = str(uuid4())
|
||||
|
||||
agentlog.debug(msg=test_string)
|
||||
agentlog.info(msg=test_string)
|
||||
agentlog.warning(msg=test_string)
|
||||
agentlog.error(msg=test_string)
|
||||
agentlog.critical(msg=test_string)
|
||||
|
||||
captured = "".join(capsys.readouterr())
|
||||
|
||||
assert test_string in captured
|
||||
assert "DEBUG" not in captured
|
||||
assert "INFO" in captured
|
||||
assert "WARNING" in captured
|
||||
assert "ERROR" in captured
|
||||
assert "CRITICAL" in captured
|
||||
|
||||
|
||||
def test_warning_agent_log_level(agentlog, capsys):
|
||||
"""Test that the debug log level logs debug agent logs and above."""
|
||||
SIM_OUTPUT.agent_log_level = LogLevel.WARNING
|
||||
SIM_OUTPUT.write_agent_log_to_terminal = True
|
||||
|
||||
test_string = str(uuid4())
|
||||
|
||||
agentlog.debug(msg=test_string)
|
||||
agentlog.info(msg=test_string)
|
||||
agentlog.warning(msg=test_string)
|
||||
agentlog.error(msg=test_string)
|
||||
agentlog.critical(msg=test_string)
|
||||
|
||||
captured = "".join(capsys.readouterr())
|
||||
|
||||
assert test_string in captured
|
||||
assert "DEBUG" not in captured
|
||||
assert "INFO" not in captured
|
||||
assert "WARNING" in captured
|
||||
assert "ERROR" in captured
|
||||
assert "CRITICAL" in captured
|
||||
|
||||
|
||||
def test_error_agent_log_level(agentlog, capsys):
|
||||
"""Test that the debug log level logs debug agent logs and above."""
|
||||
SIM_OUTPUT.agent_log_level = LogLevel.ERROR
|
||||
SIM_OUTPUT.write_agent_log_to_terminal = True
|
||||
|
||||
test_string = str(uuid4())
|
||||
|
||||
agentlog.debug(msg=test_string)
|
||||
agentlog.info(msg=test_string)
|
||||
agentlog.warning(msg=test_string)
|
||||
agentlog.error(msg=test_string)
|
||||
agentlog.critical(msg=test_string)
|
||||
|
||||
captured = "".join(capsys.readouterr())
|
||||
|
||||
assert test_string in captured
|
||||
assert "DEBUG" not in captured
|
||||
assert "INFO" not in captured
|
||||
assert "WARNING" not in captured
|
||||
assert "ERROR" in captured
|
||||
assert "CRITICAL" in captured
|
||||
|
||||
|
||||
def test_critical_agent_log_level(agentlog, capsys):
|
||||
"""Test that the debug log level logs debug agent logs and above."""
|
||||
SIM_OUTPUT.agent_log_level = LogLevel.CRITICAL
|
||||
SIM_OUTPUT.write_agent_log_to_terminal = True
|
||||
|
||||
test_string = str(uuid4())
|
||||
|
||||
agentlog.debug(msg=test_string)
|
||||
agentlog.info(msg=test_string)
|
||||
agentlog.warning(msg=test_string)
|
||||
agentlog.error(msg=test_string)
|
||||
agentlog.critical(msg=test_string)
|
||||
|
||||
captured = "".join(capsys.readouterr())
|
||||
|
||||
assert test_string in captured
|
||||
assert "DEBUG" not in captured
|
||||
assert "INFO" not in captured
|
||||
assert "WARNING" not in captured
|
||||
assert "ERROR" not in captured
|
||||
assert "CRITICAL" in captured
|
||||
@@ -26,7 +26,7 @@ def test_file_scan_request(populated_file_system):
|
||||
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
assert file.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||
|
||||
fs.apply_request(request=["file", file.name, "scan"])
|
||||
fs.apply_request(request=["folder", folder.name, "file", file.name, "scan"])
|
||||
|
||||
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
assert file.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
@@ -37,12 +37,12 @@ def test_file_checkhash_request(populated_file_system):
|
||||
"""Test that an agent can request a file hash check."""
|
||||
fs, folder, file = populated_file_system
|
||||
|
||||
fs.apply_request(request=["file", file.name, "checkhash"])
|
||||
fs.apply_request(request=["folder", folder.name, "file", file.name, "checkhash"])
|
||||
|
||||
assert file.health_status == FileSystemItemHealthStatus.GOOD
|
||||
file.sim_size = 0
|
||||
|
||||
fs.apply_request(request=["file", file.name, "checkhash"])
|
||||
fs.apply_request(request=["folder", folder.name, "file", file.name, "checkhash"])
|
||||
|
||||
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
|
||||
@@ -54,7 +54,7 @@ def test_file_repair_request(populated_file_system):
|
||||
file.corrupt()
|
||||
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
|
||||
fs.apply_request(request=["file", file.name, "repair"])
|
||||
fs.apply_request(request=["folder", folder.name, "file", file.name, "repair"])
|
||||
assert file.health_status == FileSystemItemHealthStatus.GOOD
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ def test_file_restore_request(populated_file_system):
|
||||
assert fs.get_file(folder_name=folder.name, file_name=file.name) is not None
|
||||
assert fs.get_file(folder_name=folder.name, file_name=file.name).deleted is False
|
||||
|
||||
fs.apply_request(request=["file", file.name, "corrupt"])
|
||||
fs.apply_request(request=["folder", folder.name, "file", file.name, "corrupt"])
|
||||
assert fs.get_file(folder_name=folder.name, file_name=file.name).health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
|
||||
fs.apply_request(request=["restore", "file", folder.name, file.name])
|
||||
@@ -81,7 +81,7 @@ def test_file_restore_request(populated_file_system):
|
||||
def test_file_corrupt_request(populated_file_system):
|
||||
"""Test that an agent can request a file corruption."""
|
||||
fs, folder, file = populated_file_system
|
||||
fs.apply_request(request=["file", file.name, "corrupt"])
|
||||
fs.apply_request(request=["folder", folder.name, "file", file.name, "corrupt"])
|
||||
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ def test_deleted_file_cannot_be_interacted_with(populated_file_system):
|
||||
fs, folder, file = populated_file_system
|
||||
assert fs.get_file(folder_name=folder.name, file_name=file.name) is not None
|
||||
|
||||
fs.apply_request(request=["file", file.name, "corrupt"])
|
||||
fs.apply_request(request=["folder", folder.name, "file", file.name, "corrupt"])
|
||||
assert fs.get_file(folder_name=folder.name, file_name=file.name).health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
assert (
|
||||
fs.get_file(folder_name=folder.name, file_name=file.name).visible_health_status
|
||||
|
||||
@@ -39,3 +39,39 @@ def test_folder_delete_request(populated_file_system):
|
||||
assert fs.get_file_by_id(folder_uuid=folder.uuid, file_uuid=file.uuid) is None
|
||||
|
||||
fs.show(full=True)
|
||||
|
||||
|
||||
def test_folder_exists_request_validator(populated_file_system):
|
||||
"""Tests that the _FolderExistsValidator works as intended."""
|
||||
fs, folder, file = populated_file_system
|
||||
validator = FileSystem._FolderExistsValidator(file_system=fs)
|
||||
|
||||
assert validator(request=["test_folder"], context={}) # test_folder exists
|
||||
assert validator(request=["fake_folder"], context={}) is False # fake_folder does not exist
|
||||
|
||||
assert validator.fail_message == "Cannot perform request on folder because it does not exist."
|
||||
|
||||
|
||||
def test_file_exists_request_validator(populated_file_system):
|
||||
"""Tests that the _FolderExistsValidator works as intended."""
|
||||
fs, folder, file = populated_file_system
|
||||
validator = FileSystem._FileExistsValidator(file_system=fs)
|
||||
|
||||
assert validator(request=["test_folder", "test_file.txt"], context={}) # test_file.txt exists
|
||||
assert validator(request=["test_folder", "fake_file.txt"], context={}) is False # fake_file.txt does not exist
|
||||
|
||||
assert validator.fail_message == "Cannot perform request on a file that does not exist."
|
||||
|
||||
|
||||
def test_folder_not_deleted_request_validator(populated_file_system):
|
||||
"""Tests that the _FolderExistsValidator works as intended."""
|
||||
fs, folder, file = populated_file_system
|
||||
validator = FileSystem._FolderNotDeletedValidator(file_system=fs)
|
||||
|
||||
assert validator(request=["test_folder"], context={}) # test_folder is not deleted
|
||||
|
||||
fs.delete_folder(folder_name="test_folder")
|
||||
|
||||
assert validator(request=["test_folder"], context={}) is False # test_folder is deleted
|
||||
|
||||
assert validator.fail_message == "Cannot perform request on folder because it is deleted."
|
||||
|
||||
@@ -166,15 +166,40 @@ def test_deleted_folder_and_its_files_cannot_be_interacted_with(populated_file_s
|
||||
fs, folder, file = populated_file_system
|
||||
assert fs.get_file(folder_name=folder.name, file_name=file.name) is not None
|
||||
|
||||
fs.apply_request(request=["file", file.name, "corrupt"])
|
||||
fs.apply_request(request=["folder", folder.name, "file", file.name, "corrupt"])
|
||||
assert fs.get_file(folder_name=folder.name, file_name=file.name).health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
|
||||
fs.apply_request(request=["delete", "folder", folder.name])
|
||||
assert fs.get_file(folder_name=folder.name, file_name=file.name) is None
|
||||
|
||||
fs.apply_request(request=["file", file.name, "repair"])
|
||||
fs.apply_request(request=["folder", folder.name, "file", file.name, "repair"])
|
||||
|
||||
deleted_folder = fs.deleted_folders.get(folder.uuid)
|
||||
deleted_file = deleted_folder.deleted_files.get(file.uuid)
|
||||
|
||||
assert deleted_file.health_status is not FileSystemItemHealthStatus.GOOD
|
||||
|
||||
|
||||
def test_file_exists_request_validator(populated_file_system):
|
||||
"""Tests that the _FolderExistsValidator works as intended."""
|
||||
fs, folder, file = populated_file_system
|
||||
validator = Folder._FileExistsValidator(folder=folder)
|
||||
|
||||
assert validator(request=["test_file.txt"], context={}) # test_file.txt exists
|
||||
assert validator(request=["fake_file.txt"], context={}) is False # fake_file.txt does not exist
|
||||
|
||||
assert validator.fail_message == "Cannot perform request on a file that does not exist."
|
||||
|
||||
|
||||
def test_file_not_deleted_request_validator(populated_file_system):
|
||||
"""Tests that the _FolderExistsValidator works as intended."""
|
||||
fs, folder, file = populated_file_system
|
||||
validator = Folder._FileNotDeletedValidator(folder=folder)
|
||||
|
||||
assert validator(request=["test_file.txt"], context={}) # test_file.txt is not deleted
|
||||
|
||||
fs.delete_file(folder_name="test_folder", file_name="test_file.txt")
|
||||
|
||||
assert validator(request=["fake_file.txt"], context={}) is False # test_file.txt is deleted
|
||||
|
||||
assert validator.fail_message == "Cannot perform request on a file that is deleted."
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
import pytest
|
||||
|
||||
from primaite.simulator.network.hardware.base import NetworkInterface, Node
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def node() -> Node:
|
||||
return Computer(hostname="test", ip_address="192.168.1.2", subnet_mask="255.255.255.0")
|
||||
|
||||
|
||||
def test_nic_enabled_validator(node):
|
||||
"""Test the NetworkInterface enabled validator."""
|
||||
network_interface = node.network_interface[1]
|
||||
validator = NetworkInterface._EnabledValidator(network_interface=network_interface)
|
||||
|
||||
assert validator(request=[], context={}) is False # not enabled
|
||||
|
||||
network_interface.enabled = True
|
||||
|
||||
assert validator(request=[], context={}) # enabled
|
||||
|
||||
|
||||
def test_nic_disabled_validator(node):
|
||||
"""Test the NetworkInterface enabled validator."""
|
||||
network_interface = node.network_interface[1]
|
||||
validator = NetworkInterface._DisabledValidator(network_interface=network_interface)
|
||||
|
||||
assert validator(request=[], context={}) # not enabled
|
||||
|
||||
network_interface.enabled = True
|
||||
|
||||
assert validator(request=[], context={}) is False # enabled
|
||||
@@ -155,3 +155,39 @@ def test_reset_node(node):
|
||||
assert node.operating_state == NodeOperatingState.BOOTING
|
||||
|
||||
assert node.operating_state == NodeOperatingState.ON
|
||||
|
||||
|
||||
def test_node_is_on_validator(node):
|
||||
"""Test that the node is on validator."""
|
||||
node.power_on()
|
||||
|
||||
for i in range(node.start_up_duration + 1):
|
||||
node.apply_timestep(i)
|
||||
|
||||
validator = Node._NodeIsOnValidator(node=node)
|
||||
|
||||
assert validator(request=[], context={})
|
||||
|
||||
node.power_off()
|
||||
for i in range(node.shut_down_duration + 1):
|
||||
node.apply_timestep(i)
|
||||
|
||||
assert validator(request=[], context={}) is False
|
||||
|
||||
|
||||
def test_node_is_off_validator(node):
|
||||
"""Test that the node is on validator."""
|
||||
node.power_on()
|
||||
|
||||
for i in range(node.start_up_duration + 1):
|
||||
node.apply_timestep(i)
|
||||
|
||||
validator = Node._NodeIsOffValidator(node=node)
|
||||
|
||||
assert validator(request=[], context={}) is False
|
||||
|
||||
node.power_off()
|
||||
for i in range(node.shut_down_duration + 1):
|
||||
node.apply_timestep(i)
|
||||
|
||||
assert validator(request=[], context={})
|
||||
|
||||
@@ -1 +1,15 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
from primaite.simulator.system.applications.application import Application, ApplicationOperatingState
|
||||
|
||||
|
||||
def test_application_state_validator(application):
|
||||
"""Test the application state validator."""
|
||||
validator = Application._StateValidator(application=application, state=ApplicationOperatingState.CLOSED)
|
||||
assert validator(request=[], context={}) # application is closed
|
||||
application.run()
|
||||
assert validator(request=[], context={}) is False # application is running - expecting closed
|
||||
|
||||
validator = Application._StateValidator(application=application, state=ApplicationOperatingState.RUNNING)
|
||||
assert validator(request=[], context={}) # application is running
|
||||
application.close()
|
||||
assert validator(request=[], context={}) is False # application is closed - expecting running
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
from primaite.simulator.system.services.service import ServiceOperatingState
|
||||
from primaite.simulator.system.services.service import Service, ServiceOperatingState
|
||||
from primaite.simulator.system.software import SoftwareHealthState
|
||||
|
||||
|
||||
@@ -92,3 +92,21 @@ def test_service_fix(service):
|
||||
assert service.health_state_actual == SoftwareHealthState.FIXING
|
||||
service.apply_timestep(2)
|
||||
assert service.health_state_actual == SoftwareHealthState.GOOD
|
||||
|
||||
|
||||
def test_service_state_validator(service):
|
||||
"""Test the service state validator."""
|
||||
validator = Service._StateValidator(service=service, state=ServiceOperatingState.STOPPED)
|
||||
assert validator(request=[], context={}) # service is stopped
|
||||
service.start()
|
||||
assert validator(request=[], context={}) is False # service is running - expecting stopped
|
||||
|
||||
validator = Service._StateValidator(service=service, state=ServiceOperatingState.RUNNING)
|
||||
assert validator(request=[], context={}) # service is running
|
||||
service.pause()
|
||||
assert validator(request=[], context={}) is False # service is paused - expecting running
|
||||
|
||||
validator = Service._StateValidator(service=service, state=ServiceOperatingState.PAUSED)
|
||||
assert validator(request=[], context={}) # service is paused
|
||||
service.resume()
|
||||
assert validator(request=[], context={}) is False # service is running - expecting paused
|
||||
|
||||
Reference in New Issue
Block a user