#1947: node startup/shutdown now take multiple timesteps to complete

This commit is contained in:
Czar.Echavez
2023-10-24 10:11:50 +01:00
parent 724beb1a29
commit 8b85d5d55b
8 changed files with 121 additions and 45 deletions

View File

@@ -499,9 +499,13 @@ class Folder(FileSystemItemABC):
def apply_timestep(self, timestep: int): def apply_timestep(self, timestep: int):
""" """
Used to run the actions that last over multiple timesteps. Apply a single timestep of simulation dynamics to this service.
:param: timestep: the current timestep. In this instance, if any multi-timestep processes are currently occurring (such as scanning),
then they are brought one step closer to being finished.
:param timestep: The current timestep number. (Amount of time since simulation episode began)
:type timestep: int
""" """
super().apply_timestep(timestep=timestep) super().apply_timestep(timestep=timestep)

View File

@@ -914,6 +914,18 @@ class Node(SimComponent):
revealed_to_red: bool = False revealed_to_red: bool = False
"Informs whether the node has been revealed to a red agent." "Informs whether the node has been revealed to a red agent."
start_up_duration: int = 3
"Time steps needed for the node to start up."
start_up_countdown: int = -1
"Time steps needed until node is booted up."
shut_down_duration: int = 3
"Time steps needed for the node to shut down."
shut_down_countdown: int = -1
"Time steps needed until node is shut down."
def __init__(self, **kwargs): def __init__(self, **kwargs):
""" """
Initialize the Node with various components and managers. Initialize the Node with various components and managers.
@@ -1042,9 +1054,45 @@ class Node(SimComponent):
) )
print(table) print(table)
def apply_timestep(self, timestep: int):
"""
Apply a single timestep of simulation dynamics to this service.
In this instance, if any multi-timestep processes are currently occurring
(such as starting up or shutting down), then they are brought one step closer to
being finished.
:param timestep: The current timestep number. (Amount of time since simulation episode began)
:type timestep: int
"""
super().apply_timestep(timestep=timestep)
# count down to boot up
if self.start_up_countdown > 0:
self.start_up_countdown -= 1
else:
if self.operating_state == NodeOperatingState.BOOTING:
self.operating_state = NodeOperatingState.ON
self.sys_log.info("Turned on")
for nic in self.nics.values():
if nic._connected_link:
nic.enable()
# count down to shut down
if self.shut_down_countdown > 0:
self.shut_down_countdown -= 1
else:
if self.operating_state == NodeOperatingState.SHUTTING_DOWN:
self.operating_state = NodeOperatingState.OFF
self.sys_log.info("Turned off")
def power_on(self): def power_on(self):
"""Power on the Node, enabling its NICs if it is in the OFF state.""" """Power on the Node, enabling its NICs if it is in the OFF state."""
if self.operating_state == NodeOperatingState.OFF: if self.operating_state == NodeOperatingState.OFF:
self.operating_state = NodeOperatingState.BOOTING
self.start_up_countdown = self.start_up_duration
if self.start_up_duration <= 0:
self.operating_state = NodeOperatingState.ON self.operating_state = NodeOperatingState.ON
self.sys_log.info("Turned on") self.sys_log.info("Turned on")
for nic in self.nics.values(): for nic in self.nics.values():
@@ -1056,6 +1104,10 @@ class Node(SimComponent):
if self.operating_state == NodeOperatingState.ON: if self.operating_state == NodeOperatingState.ON:
for nic in self.nics.values(): for nic in self.nics.values():
nic.disable() nic.disable()
self.operating_state = NodeOperatingState.SHUTTING_DOWN
self.shut_down_countdown = self.shut_down_duration
if self.shut_down_duration >= 0:
self.operating_state = NodeOperatingState.OFF self.operating_state = NodeOperatingState.OFF
self.sys_log.info("Turned off") self.sys_log.info("Turned off")
@@ -1135,7 +1187,7 @@ class Node(SimComponent):
f"Ping statistics for {target_ip_address}: " f"Ping statistics for {target_ip_address}: "
f"Packets: Sent = {pings}, " f"Packets: Sent = {pings}, "
f"Received = {request_replies}, " f"Received = {request_replies}, "
f"Lost = {pings-request_replies} ({(pings-request_replies)/pings*100}% loss)" f"Lost = {pings - request_replies} ({(pings - request_replies) / pings * 100}% loss)"
) )
return passed return passed
return False return False

View File

@@ -1,7 +1,7 @@
from ipaddress import IPv4Address from ipaddress import IPv4Address
from primaite.simulator.network.container import Network from primaite.simulator.network.container import Network
from primaite.simulator.network.hardware.base import NIC from primaite.simulator.network.hardware.base import NIC, NodeOperatingState
from primaite.simulator.network.hardware.nodes.computer import Computer from primaite.simulator.network.hardware.nodes.computer import Computer
from primaite.simulator.network.hardware.nodes.router import ACLAction, Router from primaite.simulator.network.hardware.nodes.router import ACLAction, Router
from primaite.simulator.network.hardware.nodes.server import Server from primaite.simulator.network.hardware.nodes.server import Server
@@ -110,19 +110,19 @@ def arcd_uc2_network() -> Network:
network = Network() network = Network()
# Router 1 # Router 1
router_1 = Router(hostname="router_1", num_ports=5) router_1 = Router(hostname="router_1", num_ports=5, operating_state=NodeOperatingState.ON)
router_1.power_on() router_1.power_on()
router_1.configure_port(port=1, ip_address="192.168.1.1", subnet_mask="255.255.255.0") router_1.configure_port(port=1, ip_address="192.168.1.1", subnet_mask="255.255.255.0")
router_1.configure_port(port=2, ip_address="192.168.10.1", subnet_mask="255.255.255.0") router_1.configure_port(port=2, ip_address="192.168.10.1", subnet_mask="255.255.255.0")
# Switch 1 # Switch 1
switch_1 = Switch(hostname="switch_1", num_ports=8) switch_1 = Switch(hostname="switch_1", num_ports=8, operating_state=NodeOperatingState.ON)
switch_1.power_on() switch_1.power_on()
network.connect(endpoint_a=router_1.ethernet_ports[1], endpoint_b=switch_1.switch_ports[8]) network.connect(endpoint_a=router_1.ethernet_ports[1], endpoint_b=switch_1.switch_ports[8])
router_1.enable_port(1) router_1.enable_port(1)
# Switch 2 # Switch 2
switch_2 = Switch(hostname="switch_2", num_ports=8) switch_2 = Switch(hostname="switch_2", num_ports=8, operating_state=NodeOperatingState.ON)
switch_2.power_on() switch_2.power_on()
network.connect(endpoint_a=router_1.ethernet_ports[2], endpoint_b=switch_2.switch_ports[8]) network.connect(endpoint_a=router_1.ethernet_ports[2], endpoint_b=switch_2.switch_ports[8])
router_1.enable_port(2) router_1.enable_port(2)
@@ -134,6 +134,7 @@ def arcd_uc2_network() -> Network:
subnet_mask="255.255.255.0", subnet_mask="255.255.255.0",
default_gateway="192.168.10.1", default_gateway="192.168.10.1",
dns_server=IPv4Address("192.168.1.10"), dns_server=IPv4Address("192.168.1.10"),
operating_state=NodeOperatingState.ON,
) )
client_1.power_on() client_1.power_on()
network.connect(endpoint_b=client_1.ethernet_port[1], endpoint_a=switch_2.switch_ports[1]) network.connect(endpoint_b=client_1.ethernet_port[1], endpoint_a=switch_2.switch_ports[1])
@@ -148,6 +149,7 @@ def arcd_uc2_network() -> Network:
subnet_mask="255.255.255.0", subnet_mask="255.255.255.0",
default_gateway="192.168.10.1", default_gateway="192.168.10.1",
dns_server=IPv4Address("192.168.1.10"), dns_server=IPv4Address("192.168.1.10"),
operating_state=NodeOperatingState.ON,
) )
client_2.power_on() client_2.power_on()
network.connect(endpoint_b=client_2.ethernet_port[1], endpoint_a=switch_2.switch_ports[2]) network.connect(endpoint_b=client_2.ethernet_port[1], endpoint_a=switch_2.switch_ports[2])
@@ -158,6 +160,7 @@ def arcd_uc2_network() -> Network:
ip_address="192.168.1.10", ip_address="192.168.1.10",
subnet_mask="255.255.255.0", subnet_mask="255.255.255.0",
default_gateway="192.168.1.1", default_gateway="192.168.1.1",
operating_state=NodeOperatingState.ON,
) )
domain_controller.power_on() domain_controller.power_on()
domain_controller.software_manager.install(DNSServer) domain_controller.software_manager.install(DNSServer)
@@ -171,6 +174,7 @@ def arcd_uc2_network() -> Network:
subnet_mask="255.255.255.0", subnet_mask="255.255.255.0",
default_gateway="192.168.1.1", default_gateway="192.168.1.1",
dns_server=IPv4Address("192.168.1.10"), dns_server=IPv4Address("192.168.1.10"),
operating_state=NodeOperatingState.ON,
) )
database_server.power_on() database_server.power_on()
network.connect(endpoint_b=database_server.ethernet_port[1], endpoint_a=switch_1.switch_ports[3]) network.connect(endpoint_b=database_server.ethernet_port[1], endpoint_a=switch_1.switch_ports[3])
@@ -244,6 +248,7 @@ def arcd_uc2_network() -> Network:
subnet_mask="255.255.255.0", subnet_mask="255.255.255.0",
default_gateway="192.168.1.1", default_gateway="192.168.1.1",
dns_server=IPv4Address("192.168.1.10"), dns_server=IPv4Address("192.168.1.10"),
operating_state=NodeOperatingState.ON,
) )
web_server.power_on() web_server.power_on()
web_server.software_manager.install(DatabaseClient) web_server.software_manager.install(DatabaseClient)
@@ -267,6 +272,7 @@ def arcd_uc2_network() -> Network:
subnet_mask="255.255.255.0", subnet_mask="255.255.255.0",
default_gateway="192.168.1.1", default_gateway="192.168.1.1",
dns_server=IPv4Address("192.168.1.10"), dns_server=IPv4Address("192.168.1.10"),
operating_state=NodeOperatingState.ON,
) )
backup_server.power_on() backup_server.power_on()
backup_server.software_manager.install(FTPServer) backup_server.software_manager.install(FTPServer)
@@ -279,6 +285,7 @@ def arcd_uc2_network() -> Network:
subnet_mask="255.255.255.0", subnet_mask="255.255.255.0",
default_gateway="192.168.1.1", default_gateway="192.168.1.1",
dns_server=IPv4Address("192.168.1.10"), dns_server=IPv4Address("192.168.1.10"),
operating_state=NodeOperatingState.ON,
) )
security_suite.power_on() security_suite.power_on()
network.connect(endpoint_b=security_suite.ethernet_port[1], endpoint_a=switch_1.switch_ports[7]) network.connect(endpoint_b=security_suite.ethernet_port[1], endpoint_a=switch_1.switch_ports[7])

View File

@@ -1,17 +1,15 @@
from primaite.simulator.network.hardware.base import Link, NIC, Node from primaite.simulator.network.hardware.base import Link, NIC, Node, NodeOperatingState
def test_node_to_node_ping(): def test_node_to_node_ping():
"""Tests two Nodes are able to ping each other.""" """Tests two Nodes are able to ping each other."""
node_a = Node(hostname="node_a") node_a = Node(hostname="node_a", operating_state=NodeOperatingState.ON)
nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0") nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0", operating_state=NodeOperatingState.ON)
node_a.connect_nic(nic_a) node_a.connect_nic(nic_a)
node_a.power_on()
node_b = Node(hostname="node_b") node_b = Node(hostname="node_b", operating_state=NodeOperatingState.ON)
nic_b = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0") nic_b = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0")
node_b.connect_nic(nic_b) node_b.connect_nic(nic_b)
node_b.power_on()
Link(endpoint_a=nic_a, endpoint_b=nic_b) Link(endpoint_a=nic_a, endpoint_b=nic_b)
@@ -20,22 +18,19 @@ def test_node_to_node_ping():
def test_multi_nic(): def test_multi_nic():
"""Tests that Nodes with multiple NICs can ping each other and the data go across the correct links.""" """Tests that Nodes with multiple NICs can ping each other and the data go across the correct links."""
node_a = Node(hostname="node_a") node_a = Node(hostname="node_a", operating_state=NodeOperatingState.ON)
nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0") nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0")
node_a.connect_nic(nic_a) node_a.connect_nic(nic_a)
node_a.power_on()
node_b = Node(hostname="node_b") node_b = Node(hostname="node_b", operating_state=NodeOperatingState.ON)
nic_b1 = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0") nic_b1 = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0")
nic_b2 = NIC(ip_address="10.0.0.12", subnet_mask="255.0.0.0") nic_b2 = NIC(ip_address="10.0.0.12", subnet_mask="255.0.0.0")
node_b.connect_nic(nic_b1) node_b.connect_nic(nic_b1)
node_b.connect_nic(nic_b2) node_b.connect_nic(nic_b2)
node_b.power_on()
node_c = Node(hostname="node_c") node_c = Node(hostname="node_c", operating_state=NodeOperatingState.ON)
nic_c = NIC(ip_address="10.0.0.13", subnet_mask="255.0.0.0") nic_c = NIC(ip_address="10.0.0.13", subnet_mask="255.0.0.0")
node_c.connect_nic(nic_c) node_c.connect_nic(nic_c)
node_c.power_on()
Link(endpoint_a=nic_a, endpoint_b=nic_b1) Link(endpoint_a=nic_a, endpoint_b=nic_b1)

View File

@@ -1,17 +1,15 @@
from primaite.simulator.network.hardware.base import Link, NIC, Node from primaite.simulator.network.hardware.base import Link, NIC, Node, NodeOperatingState
def test_link_up(): def test_link_up():
"""Tests Nodes, NICs, and Links can all be connected and be in an enabled/up state.""" """Tests Nodes, NICs, and Links can all be connected and be in an enabled/up state."""
node_a = Node(hostname="node_a") node_a = Node(hostname="node_a", operating_state=NodeOperatingState.ON)
nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0") nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0")
node_a.connect_nic(nic_a) node_a.connect_nic(nic_a)
node_a.power_on()
node_b = Node(hostname="node_b") node_b = Node(hostname="node_b", operating_state=NodeOperatingState.ON)
nic_b = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0") nic_b = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0")
node_b.connect_nic(nic_b) node_b.connect_nic(nic_b)
node_b.power_on()
link = Link(endpoint_a=nic_a, endpoint_b=nic_b) link = Link(endpoint_a=nic_a, endpoint_b=nic_b)

View File

@@ -2,7 +2,7 @@ from typing import Tuple
import pytest import pytest
from primaite.simulator.network.hardware.base import Link, NIC, Node from primaite.simulator.network.hardware.base import Link, NIC, Node, NodeOperatingState
from primaite.simulator.network.hardware.nodes.router import ACLAction, Router from primaite.simulator.network.hardware.nodes.router import ACLAction, Router
from primaite.simulator.network.transmission.network_layer import IPProtocol from primaite.simulator.network.transmission.network_layer import IPProtocol
from primaite.simulator.network.transmission.transport_layer import Port from primaite.simulator.network.transmission.transport_layer import Port
@@ -10,18 +10,15 @@ from primaite.simulator.network.transmission.transport_layer import Port
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def pc_a_pc_b_router_1() -> Tuple[Node, Node, Router]: def pc_a_pc_b_router_1() -> Tuple[Node, Node, Router]:
pc_a = Node(hostname="pc_a", default_gateway="192.168.0.1") pc_a = Node(hostname="pc_a", default_gateway="192.168.0.1", operating_state=NodeOperatingState.ON)
nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0") nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0")
pc_a.connect_nic(nic_a) pc_a.connect_nic(nic_a)
pc_a.power_on()
pc_b = Node(hostname="pc_b", default_gateway="192.168.1.1") pc_b = Node(hostname="pc_b", default_gateway="192.168.1.1", operating_state=NodeOperatingState.ON)
nic_b = NIC(ip_address="192.168.1.10", subnet_mask="255.255.255.0") nic_b = NIC(ip_address="192.168.1.10", subnet_mask="255.255.255.0")
pc_b.connect_nic(nic_b) pc_b.connect_nic(nic_b)
pc_b.power_on()
router_1 = Router(hostname="router_1") router_1 = Router(hostname="router_1", operating_state=NodeOperatingState.ON)
router_1.power_on()
router_1.configure_port(1, "192.168.0.1", "255.255.255.0") router_1.configure_port(1, "192.168.0.1", "255.255.255.0")
router_1.configure_port(2, "192.168.1.1", "255.255.255.0") router_1.configure_port(2, "192.168.1.1", "255.255.255.0")

View File

@@ -1,4 +1,4 @@
from primaite.simulator.network.hardware.base import Link from primaite.simulator.network.hardware.base import Link, NodeOperatingState
from primaite.simulator.network.hardware.nodes.computer import Computer from primaite.simulator.network.hardware.nodes.computer import Computer
from primaite.simulator.network.hardware.nodes.server import Server from primaite.simulator.network.hardware.nodes.server import Server
from primaite.simulator.network.hardware.nodes.switch import Switch from primaite.simulator.network.hardware.nodes.switch import Switch
@@ -7,17 +7,22 @@ from primaite.simulator.network.hardware.nodes.switch import Switch
def test_switched_network(): def test_switched_network():
"""Tests a node can ping another node via the switch.""" """Tests a node can ping another node via the switch."""
client_1 = Computer( client_1 = Computer(
hostname="client_1", ip_address="192.168.1.10", subnet_mask="255.255.255.0", default_gateway="192.168.1.0" hostname="client_1",
ip_address="192.168.1.10",
subnet_mask="255.255.255.0",
default_gateway="192.168.1.0",
operating_state=NodeOperatingState.ON,
) )
client_1.power_on()
server_1 = Server( server_1 = Server(
hostname=" server_1", ip_address="192.168.1.11", subnet_mask="255.255.255.0", default_gateway="192.168.1.11" hostname=" server_1",
ip_address="192.168.1.11",
subnet_mask="255.255.255.0",
default_gateway="192.168.1.11",
operating_state=NodeOperatingState.ON,
) )
server_1.power_on()
switch_1 = Switch(hostname="switch_1", num_ports=6) switch_1 = Switch(hostname="switch_1", num_ports=6, operating_state=NodeOperatingState.ON)
switch_1.power_on()
Link(endpoint_a=client_1.ethernet_port[1], endpoint_b=switch_1.switch_ports[1]) Link(endpoint_a=client_1.ethernet_port[1], endpoint_b=switch_1.switch_ports[1])
Link(endpoint_a=server_1.ethernet_port[1], endpoint_b=switch_1.switch_ports[2]) Link(endpoint_a=server_1.ethernet_port[1], endpoint_b=switch_1.switch_ports[2])

View File

@@ -11,13 +11,31 @@ def node() -> Node:
def test_node_startup(node): def test_node_startup(node):
assert node.operating_state == NodeOperatingState.OFF assert node.operating_state == NodeOperatingState.OFF
node.apply_request(["startup"]) node.apply_request(["startup"])
assert node.operating_state == NodeOperatingState.BOOTING
idx = 0
while node.operating_state == NodeOperatingState.BOOTING:
node.apply_timestep(timestep=idx)
idx += 1
assert node.operating_state == NodeOperatingState.ON assert node.operating_state == NodeOperatingState.ON
def test_node_shutdown(node): def test_node_shutdown(node):
assert node.operating_state == NodeOperatingState.OFF assert node.operating_state == NodeOperatingState.OFF
node.apply_request(["startup"]) node.apply_request(["startup"])
idx = 0
while node.operating_state == NodeOperatingState.BOOTING:
node.apply_timestep(timestep=idx)
idx += 1
assert node.operating_state == NodeOperatingState.ON assert node.operating_state == NodeOperatingState.ON
node.apply_request(["shutdown"]) node.apply_request(["shutdown"])
idx = 0
while node.operating_state == NodeOperatingState.SHUTTING_DOWN:
node.apply_timestep(timestep=idx)
idx += 1
assert node.operating_state == NodeOperatingState.OFF assert node.operating_state == NodeOperatingState.OFF