#2476 Add test for episode scheduler
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# No green agents present
|
||||
greens: &greens []
|
||||
@@ -0,0 +1,34 @@
|
||||
agents: &greens
|
||||
- ref: green_A
|
||||
team: GREEN
|
||||
type: ProbabilisticAgent
|
||||
agent_settings:
|
||||
action_probabilities:
|
||||
0: 0.2
|
||||
1: 0.8
|
||||
observation_space: null
|
||||
action_space:
|
||||
action_list:
|
||||
- type: DONOTHING
|
||||
- type: NODE_APPLICATION_EXECUTE
|
||||
options:
|
||||
nodes:
|
||||
- node_name: client
|
||||
applications:
|
||||
- application_name: DatabaseClient
|
||||
action_map:
|
||||
0:
|
||||
action: DONOTHING
|
||||
options: {}
|
||||
1:
|
||||
action: NODE_APPLICATION_EXECUTE
|
||||
options:
|
||||
node_id: 0
|
||||
application_id: 0
|
||||
|
||||
reward_function:
|
||||
reward_components:
|
||||
- type: GREEN_ADMIN_DATABASE_UNREACHABLE_PENALTY
|
||||
weight: 1.0
|
||||
options:
|
||||
node_hostname: client
|
||||
@@ -0,0 +1,34 @@
|
||||
agents: &greens
|
||||
- ref: green_B
|
||||
team: GREEN
|
||||
type: ProbabilisticAgent
|
||||
agent_settings:
|
||||
action_probabilities:
|
||||
0: 0.95
|
||||
1: 0.05
|
||||
observation_space: null
|
||||
action_space:
|
||||
action_list:
|
||||
- type: DONOTHING
|
||||
- type: NODE_APPLICATION_EXECUTE
|
||||
options:
|
||||
nodes:
|
||||
- node_name: client
|
||||
applications:
|
||||
- application_name: DatabaseClient
|
||||
action_map:
|
||||
0:
|
||||
action: DONOTHING
|
||||
options: {}
|
||||
1:
|
||||
action: NODE_APPLICATION_EXECUTE
|
||||
options:
|
||||
node_id: 0
|
||||
application_id: 0
|
||||
|
||||
reward_function:
|
||||
reward_components:
|
||||
- type: GREEN_ADMIN_DATABASE_UNREACHABLE_PENALTY
|
||||
weight: 1.0
|
||||
options:
|
||||
node_hostname: client
|
||||
@@ -0,0 +1,2 @@
|
||||
# No red agents present
|
||||
reds: &reds []
|
||||
26
tests/assets/configs/scenario_with_placeholders/reds_1.yaml
Normal file
26
tests/assets/configs/scenario_with_placeholders/reds_1.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
reds: &reds
|
||||
- ref: red_A
|
||||
team: RED
|
||||
type: RedDatabaseCorruptingAgent
|
||||
|
||||
observation_space: null
|
||||
|
||||
action_space:
|
||||
action_list:
|
||||
- type: DONOTHING
|
||||
- type: NODE_APPLICATION_EXECUTE
|
||||
options:
|
||||
nodes:
|
||||
- node_name: client
|
||||
applications:
|
||||
- application_name: DataManipulationBot
|
||||
|
||||
reward_function:
|
||||
reward_components:
|
||||
- type: DUMMY
|
||||
|
||||
agent_settings:
|
||||
start_settings:
|
||||
start_step: 10
|
||||
frequency: 10
|
||||
variance: 0
|
||||
26
tests/assets/configs/scenario_with_placeholders/reds_2.yaml
Normal file
26
tests/assets/configs/scenario_with_placeholders/reds_2.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
reds: &reds
|
||||
- ref: red_B
|
||||
team: RED
|
||||
type: RedDatabaseCorruptingAgent
|
||||
|
||||
observation_space: null
|
||||
|
||||
action_space:
|
||||
action_list:
|
||||
- type: DONOTHING
|
||||
- type: NODE_APPLICATION_EXECUTE
|
||||
options:
|
||||
nodes:
|
||||
- node_name: client
|
||||
applications:
|
||||
- application_name: DataManipulationBot
|
||||
|
||||
reward_function:
|
||||
reward_components:
|
||||
- type: DUMMY
|
||||
|
||||
agent_settings:
|
||||
start_settings:
|
||||
start_step: 3
|
||||
frequency: 2
|
||||
variance: 1
|
||||
168
tests/assets/configs/scenario_with_placeholders/scenario.yaml
Normal file
168
tests/assets/configs/scenario_with_placeholders/scenario.yaml
Normal file
@@ -0,0 +1,168 @@
|
||||
io_settings:
|
||||
save_agent_actions: true
|
||||
save_step_metadata: false
|
||||
save_pcap_logs: false
|
||||
save_sys_logs: false
|
||||
|
||||
|
||||
game:
|
||||
max_episode_length: 128
|
||||
ports:
|
||||
- HTTP
|
||||
- POSTGRES_SERVER
|
||||
protocols:
|
||||
- ICMP
|
||||
- TCP
|
||||
- UDP
|
||||
thresholds:
|
||||
nmne:
|
||||
high: 10
|
||||
medium: 5
|
||||
low: 0
|
||||
|
||||
agents:
|
||||
- *greens
|
||||
- *reds
|
||||
|
||||
- ref: defender
|
||||
team: BLUE
|
||||
type: ProxyAgent
|
||||
observation_space:
|
||||
type: CUSTOM
|
||||
options:
|
||||
components:
|
||||
- type: NODES
|
||||
label: NODES
|
||||
options:
|
||||
routers: []
|
||||
hosts:
|
||||
- hostname: client
|
||||
- hostname: server
|
||||
num_services: 1
|
||||
num_applications: 1
|
||||
num_folders: 1
|
||||
num_files: 1
|
||||
num_nics: 1
|
||||
include_num_access: false
|
||||
include_nmne: true
|
||||
|
||||
- type: LINKS
|
||||
label: LINKS
|
||||
options:
|
||||
link_references:
|
||||
- client:eth-1<->switch_1:eth-1
|
||||
- server:eth-1<->switch_1:eth-2
|
||||
|
||||
action_space:
|
||||
action_list:
|
||||
- type: DONOTHING
|
||||
- type: NODE_SHUTDOWN
|
||||
- type: NODE_STARTUP
|
||||
- type: HOST_NIC_ENABLE
|
||||
- type: HOST_NIC_DISABLE
|
||||
action_map:
|
||||
0:
|
||||
action: DONOTHING
|
||||
options: {}
|
||||
1:
|
||||
action: NODE_SHUTDOWN
|
||||
options:
|
||||
node_id: 0
|
||||
2:
|
||||
action: NODE_SHUTDOWN
|
||||
options:
|
||||
node_id: 1
|
||||
3:
|
||||
action: NODE_STARTUP
|
||||
options:
|
||||
node_id: 0
|
||||
4:
|
||||
action: NODE_STARTUP
|
||||
options:
|
||||
node_id: 1
|
||||
5:
|
||||
action: HOST_NIC_DISABLE
|
||||
options:
|
||||
node_id: 0
|
||||
nic_id: 0
|
||||
6:
|
||||
action: HOST_NIC_DISABLE
|
||||
options:
|
||||
node_id: 1
|
||||
nic_id: 0
|
||||
7:
|
||||
action: HOST_NIC_ENABLE
|
||||
options:
|
||||
node_id: 0
|
||||
nic_id: 0
|
||||
8:
|
||||
action: HOST_NIC_ENABLE
|
||||
options:
|
||||
node_id: 1
|
||||
nic_id: 0
|
||||
options:
|
||||
nodes:
|
||||
- node_name: client
|
||||
- node_name: server
|
||||
|
||||
max_folders_per_node: 0
|
||||
max_files_per_folder: 0
|
||||
max_services_per_node: 0
|
||||
max_nics_per_node: 1
|
||||
max_acl_rules: 0
|
||||
ip_list:
|
||||
- 192.168.1.2
|
||||
- 192.168.1.3
|
||||
|
||||
reward_function:
|
||||
reward_components:
|
||||
- type: DATABASE_FILE_INTEGRITY
|
||||
weight: 0.40
|
||||
options:
|
||||
node_hostname: database_server
|
||||
folder_name: database
|
||||
file_name: database.db
|
||||
|
||||
agent_settings:
|
||||
flatten_obs: false
|
||||
|
||||
|
||||
simulation:
|
||||
network:
|
||||
nodes:
|
||||
- hostname: client
|
||||
type: computer
|
||||
ip_address: 192.168.1.2
|
||||
subnet_mask: 255.255.255.0
|
||||
default_gateway: 192.168.1.1
|
||||
applications:
|
||||
- type: DatabaseClient
|
||||
options:
|
||||
db_server_ip: 192.168.1.3
|
||||
- type: DataManipulationBot
|
||||
options:
|
||||
server_ip: 192.168.1.3
|
||||
payload: "DELETE"
|
||||
|
||||
- hostname: switch_1
|
||||
type: switch
|
||||
num_ports: 2
|
||||
|
||||
- hostname: server
|
||||
type: server
|
||||
ip_address: 192.168.1.3
|
||||
subnet_mask: 255.255.255.0
|
||||
default_gateway: 192.168.1.1
|
||||
services:
|
||||
- type: DatabaseService
|
||||
|
||||
links:
|
||||
- endpoint_a_hostname: client
|
||||
endpoint_a_port: 1
|
||||
endpoint_b_hostname: switch_1
|
||||
endpoint_b_port: 1
|
||||
|
||||
- endpoint_a_hostname: server
|
||||
endpoint_a_port: 1
|
||||
endpoint_b_hostname: switch_1
|
||||
endpoint_b_port: 2
|
||||
@@ -0,0 +1,14 @@
|
||||
base_scenario: scenario.yaml
|
||||
schedule:
|
||||
0:
|
||||
- greens_0.yaml
|
||||
- reds_0.yaml
|
||||
1:
|
||||
- greens_0.yaml
|
||||
- reds_1.yaml
|
||||
2:
|
||||
- greens_1.yaml
|
||||
- reds_1.yaml
|
||||
3:
|
||||
- greens_2.yaml
|
||||
- reds_2.yaml
|
||||
@@ -16,7 +16,7 @@ def test_sb3_compatibility():
|
||||
with open(data_manipulation_config_path(), "r") as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
|
||||
gym = PrimaiteGymEnv(game_config=cfg)
|
||||
gym = PrimaiteGymEnv(env_config=cfg)
|
||||
model = PPO("MlpPolicy", gym)
|
||||
|
||||
model.learn(total_timesteps=1000)
|
||||
|
||||
@@ -21,7 +21,7 @@ class TestPrimaiteEnvironment:
|
||||
"""Check that environment loads correctly from config and it can be reset."""
|
||||
with open(CFG_PATH, "r") as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
env = PrimaiteGymEnv(game_config=cfg)
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
|
||||
def env_checks():
|
||||
assert env is not None
|
||||
@@ -44,7 +44,7 @@ class TestPrimaiteEnvironment:
|
||||
"""Make sure you can go all the way through the session without errors."""
|
||||
with open(CFG_PATH, "r") as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
env = PrimaiteGymEnv(game_config=cfg)
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
|
||||
assert (num_actions := len(env.agent.action_manager.action_map)) == 54
|
||||
# run every action and make sure there's no crash
|
||||
@@ -88,4 +88,4 @@ class TestPrimaiteEnvironment:
|
||||
with open(MISCONFIGURED_PATH, "r") as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
with pytest.raises(pydantic.ValidationError):
|
||||
env = PrimaiteGymEnv(game_config=cfg)
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
|
||||
@@ -44,7 +44,7 @@ def test_application_install_uninstall_on_uc2():
|
||||
with open(TEST_ASSETS_ROOT / "configs/test_application_install.yaml", "r") as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
|
||||
env = PrimaiteGymEnv(game_config=cfg)
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
env.agent.flatten_obs = False
|
||||
env.reset()
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from primaite.session.environment import PrimaiteGymEnv, PrimaiteRayEnv, PrimaiteRayMARLEnv
|
||||
from tests.conftest import TEST_ASSETS_ROOT
|
||||
|
||||
folder_path = TEST_ASSETS_ROOT / "configs" / "scenario_with_placeholders"
|
||||
single_yaml_config = TEST_ASSETS_ROOT / "configs" / "test_primaite_session.yaml"
|
||||
with open(single_yaml_config, "r") as f:
|
||||
config_dict = yaml.safe_load(f)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("env_type", [PrimaiteGymEnv, PrimaiteRayEnv, PrimaiteRayMARLEnv])
|
||||
def test_creating_env_with_folder(env_type):
|
||||
"""Check that the environment can be created with a folder path."""
|
||||
|
||||
def check_taking_steps(e):
|
||||
if isinstance(e, PrimaiteRayMARLEnv):
|
||||
for i in range(9):
|
||||
e.step({k: i for k in e.game.rl_agents})
|
||||
else:
|
||||
for i in range(9):
|
||||
e.step(i)
|
||||
|
||||
env = env_type(env_config=folder_path)
|
||||
assert env is not None
|
||||
for _ in range(3): # do it multiple times to ensure it loops back to the beginning
|
||||
assert len(env.game.agents) == 1
|
||||
assert "defender" in env.game.agents
|
||||
check_taking_steps(env)
|
||||
|
||||
env.reset()
|
||||
assert len(env.game.agents) == 2
|
||||
assert "defender" in env.game.agents
|
||||
assert "red_A" in env.game.agents
|
||||
check_taking_steps(env)
|
||||
|
||||
env.reset()
|
||||
assert len(env.game.agents) == 3
|
||||
assert all([a in env.game.agents for a in ["defender", "green_A", "red_A"]])
|
||||
check_taking_steps(env)
|
||||
|
||||
env.reset()
|
||||
assert len(env.game.agents) == 3
|
||||
assert all([a in env.game.agents for a in ["defender", "green_B", "red_B"]])
|
||||
check_taking_steps(env)
|
||||
|
||||
env.reset()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"env_data, env_type",
|
||||
[
|
||||
(single_yaml_config, PrimaiteGymEnv),
|
||||
(single_yaml_config, PrimaiteRayEnv),
|
||||
(single_yaml_config, PrimaiteRayMARLEnv),
|
||||
(config_dict, PrimaiteGymEnv),
|
||||
(config_dict, PrimaiteRayEnv),
|
||||
(config_dict, PrimaiteRayMARLEnv),
|
||||
],
|
||||
)
|
||||
def test_creating_env_with_static_config(env_data, env_type):
|
||||
"""Check that the environment can be created with a single yaml file."""
|
||||
env = env_type(env_config=single_yaml_config)
|
||||
assert env is not None
|
||||
agents_before = len(env.game.agents)
|
||||
env.reset()
|
||||
assert len(env.game.agents) == agents_before
|
||||
@@ -24,7 +24,7 @@ def test_io_settings():
|
||||
"""Test that the io_settings are loaded correctly."""
|
||||
with open(BASIC_CONFIG, "r") as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
env = PrimaiteGymEnv(game_config=cfg)
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
|
||||
assert env.io.settings is not None
|
||||
|
||||
|
||||
@@ -507,7 +507,7 @@ def test_firewall_acl_add_remove_rule_integration():
|
||||
with open(FIREWALL_ACTIONS_NETWORK, "r") as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
|
||||
env = PrimaiteGymEnv(game_config=cfg)
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
|
||||
# 1: Check that traffic is normal and acl starts off with 4 rules.
|
||||
firewall = env.game.simulation.network.get_node_by_hostname("firewall")
|
||||
@@ -598,7 +598,7 @@ def test_firewall_port_disable_enable_integration():
|
||||
with open(FIREWALL_ACTIONS_NETWORK, "r") as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
|
||||
env = PrimaiteGymEnv(game_config=cfg)
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
firewall = env.game.simulation.network.get_node_by_hostname("firewall")
|
||||
|
||||
assert firewall.dmz_port.enabled == True
|
||||
|
||||
@@ -103,7 +103,7 @@ def test_shared_reward():
|
||||
with open(CFG_PATH, "r") as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
|
||||
env = PrimaiteGymEnv(game_config=cfg)
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
|
||||
env.reset()
|
||||
|
||||
|
||||
0
tests/unit_tests/_primaite/_session/__init__.py
Normal file
0
tests/unit_tests/_primaite/_session/__init__.py
Normal file
52
tests/unit_tests/_primaite/_session/test_episode_schedule.py
Normal file
52
tests/unit_tests/_primaite/_session/test_episode_schedule.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# FILEPATH: /home/cade/repos/PrimAITE/tests/unit_tests/_primaite/_session/test_episode_schedule.py
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from primaite.session.episode_schedule import ConstantEpisodeScheduler, EpisodeListScheduler
|
||||
|
||||
|
||||
def test_episode_list_scheduler():
|
||||
# Initialize an instance of EpisodeListScheduler
|
||||
|
||||
# Define a schedule and episode data for testing
|
||||
schedule = {0: ["episode1"], 1: ["episode2"]}
|
||||
episode_data = {"episode1": "data1: 1", "episode2": "data2: 2"}
|
||||
base_scenario = """agents: []"""
|
||||
|
||||
scheduler = EpisodeListScheduler(schedule=schedule, episode_data=episode_data, base_scenario=base_scenario)
|
||||
# Test when episode number is within the schedule
|
||||
result = scheduler(0)
|
||||
assert isinstance(result, dict)
|
||||
assert yaml.safe_load("data1: 1\nagents: []") == result
|
||||
|
||||
# Test next episode
|
||||
result = scheduler(1)
|
||||
assert isinstance(result, dict)
|
||||
assert yaml.safe_load("data2: 2\nagents: []") == result
|
||||
|
||||
# Test when episode number exceeds the schedule
|
||||
result = scheduler(2)
|
||||
assert isinstance(result, dict)
|
||||
assert yaml.safe_load("data1: 1\nagents: []") == result
|
||||
assert scheduler._exceeded_episode_list
|
||||
|
||||
# Test when episode number is a sequence
|
||||
scheduler.schedule = {0: ["episode1", "episode2"]}
|
||||
result = scheduler(0)
|
||||
assert isinstance(result, dict)
|
||||
assert yaml.safe_load("data1: 1\ndata2: 2\nagents: []") == result
|
||||
|
||||
|
||||
def test_constant_episode_scheduler():
|
||||
# Initialize an instance of ConstantEpisodeScheduler
|
||||
config = {"key": "value"}
|
||||
scheduler = ConstantEpisodeScheduler(config=config)
|
||||
|
||||
result = scheduler(0)
|
||||
assert isinstance(result, dict)
|
||||
assert {"key": "value"} == result
|
||||
|
||||
result = scheduler(1)
|
||||
assert isinstance(result, dict)
|
||||
assert {"key": "value"} == result
|
||||
Reference in New Issue
Block a user