Merged PR 217: #2064: fix for services still running after node is turned off
## Summary - Added a check for services so that they should not do anything unless the node it is installed in is turned on ## Test process - Added tests everywhere ## Checklist - [X] PR is linked to a **work item** - [X] **acceptance criteria** of linked ticket are met - [X] performed **self-review** of the code - [X] written **tests** for any new functionality added with this PR - [X] updated the **documentation** if this PR changes or adds functionality - [ ] written/updated **design docs** if this PR implements new functionality - [X] updated the **change log** - [X] ran **pre-commit** checks for code style - [X] attended to any **TO-DOs** left in the code Related work items: #2064
This commit is contained in:
@@ -35,6 +35,7 @@ SessionManager.
|
|||||||
- DNS Services: `DNSClient` and `DNSServer`
|
- DNS Services: `DNSClient` and `DNSServer`
|
||||||
- FTP Services: `FTPClient` and `FTPServer`
|
- FTP Services: `FTPClient` and `FTPServer`
|
||||||
- HTTP Services: `WebBrowser` to simulate a web client and `WebServer`
|
- HTTP Services: `WebBrowser` to simulate a web client and `WebServer`
|
||||||
|
- Fixed an issue where the services were still able to run even though the node the service is installed on is turned off
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- Removed legacy simulation modules: `acl`, `common`, `environment`, `links`, `nodes`, `pol`
|
- Removed legacy simulation modules: `acl`, `common`, `environment`, `links`, `nodes`, `pol`
|
||||||
|
|||||||
@@ -109,6 +109,67 @@ e.g.
|
|||||||
instant_start_node = Node(hostname="client", start_up_duration=0, shut_down_duration=0)
|
instant_start_node = Node(hostname="client", start_up_duration=0, shut_down_duration=0)
|
||||||
instant_start_node.power_on() # node will still need to be powered on
|
instant_start_node.power_on() # node will still need to be powered on
|
||||||
|
|
||||||
|
.. _Node Start up and Shut down:
|
||||||
|
|
||||||
|
---------------------------
|
||||||
|
Node Start up and Shut down
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Nodes are powered on and off over multiple timesteps. By default, the node ``start_up_duration`` and ``shut_down_duration`` is 3 timesteps.
|
||||||
|
|
||||||
|
Example code where a node is turned on:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from primaite.simulator.network.hardware.base import Node
|
||||||
|
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||||
|
|
||||||
|
node = Node(hostname="pc_a")
|
||||||
|
|
||||||
|
assert node.operating_state is NodeOperatingState.OFF # By default, node is instantiated in an OFF state
|
||||||
|
|
||||||
|
node.power_on() # power on the node
|
||||||
|
|
||||||
|
assert node.operating_state is NodeOperatingState.BOOTING # node is booting up
|
||||||
|
|
||||||
|
for i in range(node.start_up_duration + 1):
|
||||||
|
# apply timestep until the node start up duration
|
||||||
|
node.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
assert node.operating_state is NodeOperatingState.ON # node is in ON state
|
||||||
|
|
||||||
|
|
||||||
|
If the node needs to be instantiated in an on state:
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from primaite.simulator.network.hardware.base import Node
|
||||||
|
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||||
|
|
||||||
|
node = Node(hostname="pc_a", operating_state=NodeOperatingState.ON)
|
||||||
|
|
||||||
|
assert node.operating_state is NodeOperatingState.ON # node is in ON state
|
||||||
|
|
||||||
|
Setting ``start_up_duration`` and/or ``shut_down_duration`` to ``0`` will allow for the ``power_on`` and ``power_off`` methods to be completed instantly without applying timesteps:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from primaite.simulator.network.hardware.base import Node
|
||||||
|
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||||
|
|
||||||
|
node = Node(hostname="pc_a", start_up_duration=0, shut_down_duration=0)
|
||||||
|
|
||||||
|
assert node.operating_state is NodeOperatingState.OFF # node is in OFF state
|
||||||
|
|
||||||
|
node.power_on()
|
||||||
|
|
||||||
|
assert node.operating_state is NodeOperatingState.ON # node is in ON state
|
||||||
|
|
||||||
|
node.power_off()
|
||||||
|
|
||||||
|
assert node.operating_state is NodeOperatingState.OFF # node is in OFF state
|
||||||
|
|
||||||
------------------
|
------------------
|
||||||
Network Interfaces
|
Network Interfaces
|
||||||
------------------
|
------------------
|
||||||
@@ -357,7 +418,6 @@ Creating the four nodes results in:
|
|||||||
|
|
||||||
2023-08-08 15:50:08,359 INFO: Connected NIC 84:20:7c:ec:a5:c6/192.168.0.13
|
2023-08-08 15:50:08,359 INFO: Connected NIC 84:20:7c:ec:a5:c6/192.168.0.13
|
||||||
|
|
||||||
|
|
||||||
---------------
|
---------------
|
||||||
Create Switches
|
Create Switches
|
||||||
---------------
|
---------------
|
||||||
|
|||||||
@@ -35,9 +35,12 @@ Example
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
client_1 = Computer(
|
client_1 = Computer(
|
||||||
hostname="client_1", ip_address="192.168.10.21", subnet_mask="255.255.255.0", default_gateway="192.168.10.1"
|
hostname="client_1",
|
||||||
|
ip_address="192.168.10.21",
|
||||||
|
subnet_mask="255.255.255.0",
|
||||||
|
default_gateway="192.168.10.1"
|
||||||
|
operating_state=NodeOperatingState.ON # initialise the computer in an ON state
|
||||||
)
|
)
|
||||||
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])
|
||||||
client_1.software_manager.install(DataManipulationBot)
|
client_1.software_manager.install(DataManipulationBot)
|
||||||
data_manipulation_bot: DataManipulationBot = client_1.software_manager.software["DataManipulationBot"]
|
data_manipulation_bot: DataManipulationBot = client_1.software_manager.software["DataManipulationBot"]
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ Dependencies
|
|||||||
from primaite.simulator.network.hardware.nodes.server import Server
|
from primaite.simulator.network.hardware.nodes.server import Server
|
||||||
from primaite.simulator.system.services.ftp.ftp_server import FTPServer
|
from primaite.simulator.system.services.ftp.ftp_server import FTPServer
|
||||||
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
||||||
|
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||||
|
|
||||||
Example peer to peer network
|
Example peer to peer network
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@@ -85,10 +86,18 @@ Example peer to peer network
|
|||||||
|
|
||||||
net = Network()
|
net = Network()
|
||||||
|
|
||||||
pc1 = Computer(hostname="pc1", ip_address="120.10.10.10", subnet_mask="255.255.255.0")
|
pc1 = Computer(
|
||||||
srv = Server(hostname="srv", ip_address="120.10.10.20", subnet_mask="255.255.255.0")
|
hostname="pc1",
|
||||||
pc1.power_on()
|
ip_address="120.10.10.10",
|
||||||
srv.power_on()
|
subnet_mask="255.255.255.0",
|
||||||
|
operating_state=NodeOperatingState.ON # initialise the computer in an ON state
|
||||||
|
)
|
||||||
|
srv = Server(
|
||||||
|
hostname="srv",
|
||||||
|
ip_address="120.10.10.20",
|
||||||
|
subnet_mask="255.255.255.0",
|
||||||
|
operating_state=NodeOperatingState.ON # initialise the server in an ON state
|
||||||
|
)
|
||||||
net.connect(pc1.ethernet_port[1], srv.ethernet_port[1])
|
net.connect(pc1.ethernet_port[1], srv.ethernet_port[1])
|
||||||
|
|
||||||
Install the FTP Server
|
Install the FTP Server
|
||||||
|
|||||||
@@ -6,14 +6,45 @@
|
|||||||
Software
|
Software
|
||||||
========
|
========
|
||||||
|
|
||||||
|
-------------
|
||||||
|
Base Software
|
||||||
|
-------------
|
||||||
|
|
||||||
|
All software which inherits ``IOSoftware`` installed on a node will not work unless the node has been turned on.
|
||||||
|
|
||||||
|
See :ref:`Node Start up and Shut down`
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from primaite.simulator.network.hardware.base import Node
|
||||||
|
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||||
|
from primaite.simulator.system.services.service import ServiceOperatingState
|
||||||
|
from primaite.simulator.system.services.web_server.web_server import WebServer
|
||||||
|
|
||||||
|
node = Node(hostname="pc_a", start_up_duration=0, shut_down_duration=0)
|
||||||
|
|
||||||
|
node.power_on()
|
||||||
|
assert node.operating_state is NodeOperatingState.ON
|
||||||
|
|
||||||
|
node.software_manager.install(WebServer)
|
||||||
|
|
||||||
|
web_server: WebServer = node.software_manager.software["WebServer"]
|
||||||
|
assert web_server.operating_state is ServiceOperatingState.RUNNING # service is immediately ran after install
|
||||||
|
|
||||||
|
node.power_off()
|
||||||
|
assert node.operating_state is NodeOperatingState.OFF
|
||||||
|
assert web_server.operating_state is ServiceOperatingState.STOPPED # service stops when node is powered off
|
||||||
|
|
||||||
|
node.power_on()
|
||||||
|
assert node.operating_state is NodeOperatingState.ON
|
||||||
|
assert web_server.operating_state is ServiceOperatingState.RUNNING # service turned back on when node is powered on
|
||||||
|
|
||||||
|
|
||||||
|
Services, Processes and Applications:
|
||||||
Contents
|
#####################################
|
||||||
########
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 8
|
:maxdepth: 2
|
||||||
|
|
||||||
database_client_server
|
database_client_server
|
||||||
data_manipulation_bot
|
data_manipulation_bot
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ class FolderObservation(AbstractObservation):
|
|||||||
self.files.append(FileObservation())
|
self.files.append(FileObservation())
|
||||||
while len(self.files) > num_files_per_folder:
|
while len(self.files) > num_files_per_folder:
|
||||||
truncated_file = self.files.pop()
|
truncated_file = self.files.pop()
|
||||||
msg = f"Too many files in folde observation. Truncating file {truncated_file}"
|
msg = f"Too many files in folder observation. Truncating file {truncated_file}"
|
||||||
_LOGGER.warn(msg)
|
_LOGGER.warn(msg)
|
||||||
|
|
||||||
self.default_observation = {
|
self.default_observation = {
|
||||||
|
|||||||
@@ -52,6 +52,17 @@ class Network(SimComponent):
|
|||||||
)
|
)
|
||||||
return rm
|
return rm
|
||||||
|
|
||||||
|
def apply_timestep(self, timestep: int) -> None:
|
||||||
|
"""Apply a timestep evolution to this the network and its nodes and links."""
|
||||||
|
super().apply_timestep(timestep=timestep)
|
||||||
|
# apply timestep to nodes
|
||||||
|
for node_id in self.nodes:
|
||||||
|
self.nodes[node_id].apply_timestep(timestep=timestep)
|
||||||
|
|
||||||
|
# apply timestep to links
|
||||||
|
for link_id in self.links:
|
||||||
|
self.links[link_id].apply_timestep(timestep=timestep)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def routers(self) -> List[Router]:
|
def routers(self) -> List[Router]:
|
||||||
"""The Routers in the Network."""
|
"""The Routers in the Network."""
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
from enum import Enum
|
|
||||||
from ipaddress import IPv4Address, IPv4Network
|
from ipaddress import IPv4Address, IPv4Network
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Literal, Optional, Tuple, Union
|
from typing import Any, Dict, Literal, Optional, Tuple, Union
|
||||||
@@ -15,6 +14,7 @@ from primaite.simulator import SIM_OUTPUT
|
|||||||
from primaite.simulator.core import RequestManager, RequestType, SimComponent
|
from primaite.simulator.core import RequestManager, RequestType, SimComponent
|
||||||
from primaite.simulator.domain.account import Account
|
from primaite.simulator.domain.account import Account
|
||||||
from primaite.simulator.file_system.file_system import FileSystem
|
from primaite.simulator.file_system.file_system import FileSystem
|
||||||
|
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||||
from primaite.simulator.network.protocols.arp import ARPEntry, ARPPacket
|
from primaite.simulator.network.protocols.arp import ARPEntry, ARPPacket
|
||||||
from primaite.simulator.network.transmission.data_link_layer import EthernetHeader, Frame
|
from primaite.simulator.network.transmission.data_link_layer import EthernetHeader, Frame
|
||||||
from primaite.simulator.network.transmission.network_layer import ICMPPacket, ICMPType, IPPacket, IPProtocol
|
from primaite.simulator.network.transmission.network_layer import ICMPPacket, ICMPType, IPPacket, IPProtocol
|
||||||
@@ -856,19 +856,6 @@ class ICMP:
|
|||||||
return sequence, icmp_packet.identifier
|
return sequence, icmp_packet.identifier
|
||||||
|
|
||||||
|
|
||||||
class NodeOperatingState(Enum):
|
|
||||||
"""Enumeration of Node Operating States."""
|
|
||||||
|
|
||||||
ON = 1
|
|
||||||
"The node is powered on."
|
|
||||||
OFF = 2
|
|
||||||
"The node is powered off."
|
|
||||||
BOOTING = 3
|
|
||||||
"The node is in the process of booting up."
|
|
||||||
SHUTTING_DOWN = 4
|
|
||||||
"The node is in the process of shutting down."
|
|
||||||
|
|
||||||
|
|
||||||
class Node(SimComponent):
|
class Node(SimComponent):
|
||||||
"""
|
"""
|
||||||
A basic Node class that represents a node on the network.
|
A basic Node class that represents a node on the network.
|
||||||
@@ -1090,18 +1077,21 @@ class Node(SimComponent):
|
|||||||
else:
|
else:
|
||||||
if self.operating_state == NodeOperatingState.BOOTING:
|
if self.operating_state == NodeOperatingState.BOOTING:
|
||||||
self.operating_state = NodeOperatingState.ON
|
self.operating_state = NodeOperatingState.ON
|
||||||
self.sys_log.info("Turned on")
|
self.sys_log.info(f"{self.hostname}: Turned on")
|
||||||
for nic in self.nics.values():
|
for nic in self.nics.values():
|
||||||
if nic._connected_link:
|
if nic._connected_link:
|
||||||
nic.enable()
|
nic.enable()
|
||||||
|
|
||||||
|
self._start_up_actions()
|
||||||
|
|
||||||
# count down to shut down
|
# count down to shut down
|
||||||
if self.shut_down_countdown > 0:
|
if self.shut_down_countdown > 0:
|
||||||
self.shut_down_countdown -= 1
|
self.shut_down_countdown -= 1
|
||||||
else:
|
else:
|
||||||
if self.operating_state == NodeOperatingState.SHUTTING_DOWN:
|
if self.operating_state == NodeOperatingState.SHUTTING_DOWN:
|
||||||
self.operating_state = NodeOperatingState.OFF
|
self.operating_state = NodeOperatingState.OFF
|
||||||
self.sys_log.info("Turned off")
|
self.sys_log.info(f"{self.hostname}: Turned off")
|
||||||
|
self._shut_down_actions()
|
||||||
|
|
||||||
# if resetting turn back on
|
# if resetting turn back on
|
||||||
if self.is_resetting:
|
if self.is_resetting:
|
||||||
@@ -1197,6 +1187,7 @@ class Node(SimComponent):
|
|||||||
self.start_up_countdown = self.start_up_duration
|
self.start_up_countdown = self.start_up_duration
|
||||||
|
|
||||||
if self.start_up_duration <= 0:
|
if self.start_up_duration <= 0:
|
||||||
|
self._start_up_actions()
|
||||||
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():
|
||||||
@@ -1212,6 +1203,7 @@ class Node(SimComponent):
|
|||||||
self.shut_down_countdown = self.shut_down_duration
|
self.shut_down_countdown = self.shut_down_duration
|
||||||
|
|
||||||
if self.shut_down_duration <= 0:
|
if self.shut_down_duration <= 0:
|
||||||
|
self._shut_down_actions()
|
||||||
self.operating_state = NodeOperatingState.OFF
|
self.operating_state = NodeOperatingState.OFF
|
||||||
self.sys_log.info("Turned off")
|
self.sys_log.info("Turned off")
|
||||||
|
|
||||||
@@ -1418,6 +1410,34 @@ class Node(SimComponent):
|
|||||||
_LOGGER.info(f"Removed application {application.uuid} from node {self.uuid}")
|
_LOGGER.info(f"Removed application {application.uuid} from node {self.uuid}")
|
||||||
self._application_request_manager.remove_request(application.uuid)
|
self._application_request_manager.remove_request(application.uuid)
|
||||||
|
|
||||||
|
def _shut_down_actions(self):
|
||||||
|
"""Actions to perform when the node is shut down."""
|
||||||
|
# Turn off all the services in the node
|
||||||
|
for service_id in self.services:
|
||||||
|
self.services[service_id].stop()
|
||||||
|
|
||||||
|
# Turn off all the applications in the node
|
||||||
|
for app_id in self.applications:
|
||||||
|
self.applications[app_id].close()
|
||||||
|
|
||||||
|
# Turn off all processes in the node
|
||||||
|
# for process_id in self.processes:
|
||||||
|
# self.processes[process_id]
|
||||||
|
|
||||||
|
def _start_up_actions(self):
|
||||||
|
"""Actions to perform when the node is starting up."""
|
||||||
|
# Turn on all the services in the node
|
||||||
|
for service_id in self.services:
|
||||||
|
self.services[service_id].start()
|
||||||
|
|
||||||
|
# Turn on all the applications in the node
|
||||||
|
for app_id in self.applications:
|
||||||
|
self.applications[app_id].run()
|
||||||
|
|
||||||
|
# Turn off all processes in the node
|
||||||
|
# for process_id in self.processes:
|
||||||
|
# self.processes[process_id]
|
||||||
|
|
||||||
def __contains__(self, item: Any) -> bool:
|
def __contains__(self, item: Any) -> bool:
|
||||||
if isinstance(item, Service):
|
if isinstance(item, Service):
|
||||||
return item.uuid in self.services
|
return item.uuid in self.services
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class NodeOperatingState(Enum):
|
||||||
|
"""Enumeration of Node Operating States."""
|
||||||
|
|
||||||
|
ON = 1
|
||||||
|
"The node is powered on."
|
||||||
|
OFF = 2
|
||||||
|
"The node is powered off."
|
||||||
|
BOOTING = 3
|
||||||
|
"The node is in the process of booting up."
|
||||||
|
SHUTTING_DOWN = 4
|
||||||
|
"The node is in the process of shutting down."
|
||||||
@@ -35,6 +35,9 @@ class FTPCommand(Enum):
|
|||||||
class FTPStatusCode(Enum):
|
class FTPStatusCode(Enum):
|
||||||
"""Status code of the current FTP request."""
|
"""Status code of the current FTP request."""
|
||||||
|
|
||||||
|
NOT_FOUND = 14
|
||||||
|
"""Destination not found."""
|
||||||
|
|
||||||
OK = 200
|
OK = 200
|
||||||
"""Command successful."""
|
"""Command successful."""
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ from abc import abstractmethod
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Dict, Set
|
from typing import Any, Dict, Set
|
||||||
|
|
||||||
|
from primaite import getLogger
|
||||||
from primaite.simulator.system.software import IOSoftware, SoftwareHealthState
|
from primaite.simulator.system.software import IOSoftware, SoftwareHealthState
|
||||||
|
|
||||||
|
_LOGGER = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ApplicationOperatingState(Enum):
|
class ApplicationOperatingState(Enum):
|
||||||
"""Enumeration of Application Operating States."""
|
"""Enumeration of Application Operating States."""
|
||||||
@@ -51,7 +54,7 @@ class Application(IOSoftware):
|
|||||||
state = super().describe_state()
|
state = super().describe_state()
|
||||||
state.update(
|
state.update(
|
||||||
{
|
{
|
||||||
"opearting_state": self.operating_state.value,
|
"operating_state": self.operating_state.value,
|
||||||
"execution_control_status": self.execution_control_status,
|
"execution_control_status": self.execution_control_status,
|
||||||
"num_executions": self.num_executions,
|
"num_executions": self.num_executions,
|
||||||
"groups": list(self.groups),
|
"groups": list(self.groups),
|
||||||
@@ -59,8 +62,30 @@ class Application(IOSoftware):
|
|||||||
)
|
)
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
def _can_perform_action(self) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if the application can perform actions.
|
||||||
|
|
||||||
|
This is done by checking if the application is operating properly or the node it is installed
|
||||||
|
in is operational.
|
||||||
|
|
||||||
|
Returns true if the software can perform actions.
|
||||||
|
"""
|
||||||
|
if not super()._can_perform_action():
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.operating_state is not self.operating_state.RUNNING:
|
||||||
|
# service is not running
|
||||||
|
_LOGGER.error(f"Cannot perform action: {self.name} is {self.operating_state.name}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""Open the Application."""
|
"""Open the Application."""
|
||||||
|
if not super()._can_perform_action():
|
||||||
|
return
|
||||||
|
|
||||||
if self.operating_state == ApplicationOperatingState.CLOSED:
|
if self.operating_state == ApplicationOperatingState.CLOSED:
|
||||||
self.sys_log.info(f"Running Application {self.name}")
|
self.sys_log.info(f"Running Application {self.name}")
|
||||||
self.operating_state = ApplicationOperatingState.RUNNING
|
self.operating_state = ApplicationOperatingState.RUNNING
|
||||||
@@ -73,6 +98,9 @@ class Application(IOSoftware):
|
|||||||
|
|
||||||
def install(self) -> None:
|
def install(self) -> None:
|
||||||
"""Install Application."""
|
"""Install Application."""
|
||||||
|
if self._can_perform_action():
|
||||||
|
return
|
||||||
|
|
||||||
super().install()
|
super().install()
|
||||||
if self.operating_state == ApplicationOperatingState.CLOSED:
|
if self.operating_state == ApplicationOperatingState.CLOSED:
|
||||||
self.sys_log.info(f"Installing Application {self.name}")
|
self.sys_log.info(f"Installing Application {self.name}")
|
||||||
@@ -97,4 +125,4 @@ class Application(IOSoftware):
|
|||||||
:param payload: The payload to receive.
|
:param payload: The payload to receive.
|
||||||
:return: True if successful, False otherwise.
|
:return: True if successful, False otherwise.
|
||||||
"""
|
"""
|
||||||
pass
|
return super().receive(payload=payload, session_id=session_id, **kwargs)
|
||||||
|
|||||||
@@ -54,7 +54,10 @@ class DatabaseClient(Application):
|
|||||||
|
|
||||||
def connect(self) -> bool:
|
def connect(self) -> bool:
|
||||||
"""Connect to a Database Service."""
|
"""Connect to a Database Service."""
|
||||||
if not self.connected and self.operating_state.RUNNING:
|
if not self._can_perform_action():
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.connected:
|
||||||
return self._connect(self.server_ip_address, self.server_password)
|
return self._connect(self.server_ip_address, self.server_password)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -135,19 +138,31 @@ class DatabaseClient(Application):
|
|||||||
self.operating_state = ApplicationOperatingState.RUNNING
|
self.operating_state = ApplicationOperatingState.RUNNING
|
||||||
self.connect()
|
self.connect()
|
||||||
|
|
||||||
def query(self, sql: str) -> bool:
|
def query(self, sql: str, is_reattempt: bool = False) -> bool:
|
||||||
"""
|
"""
|
||||||
Send a query to the Database Service.
|
Send a query to the Database Service.
|
||||||
|
|
||||||
:param sql: The SQL query.
|
:param: sql: The SQL query.
|
||||||
|
:param: is_reattempt: If true, the action has been reattempted.
|
||||||
:return: True if the query was successful, otherwise False.
|
:return: True if the query was successful, otherwise False.
|
||||||
"""
|
"""
|
||||||
if self.connected and self.operating_state.RUNNING:
|
if not self._can_perform_action():
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.connected:
|
||||||
query_id = str(uuid4())
|
query_id = str(uuid4())
|
||||||
|
|
||||||
# Initialise the tracker of this ID to False
|
# Initialise the tracker of this ID to False
|
||||||
self._query_success_tracker[query_id] = False
|
self._query_success_tracker[query_id] = False
|
||||||
return self._query(sql=sql, query_id=query_id)
|
return self._query(sql=sql, query_id=query_id)
|
||||||
|
else:
|
||||||
|
if is_reattempt:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.connect():
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.query(sql=sql, is_reattempt=True)
|
||||||
|
|
||||||
def receive(self, payload: Any, session_id: str, **kwargs) -> bool:
|
def receive(self, payload: Any, session_id: str, **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ from ipaddress import IPv4Address
|
|||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from primaite.simulator.network.protocols.http import HttpRequestMethod, HttpRequestPacket, HttpResponsePacket
|
from primaite.simulator.network.protocols.http import (
|
||||||
|
HttpRequestMethod,
|
||||||
|
HttpRequestPacket,
|
||||||
|
HttpResponsePacket,
|
||||||
|
HttpStatusCode,
|
||||||
|
)
|
||||||
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
|
||||||
from primaite.simulator.system.applications.application import Application
|
from primaite.simulator.system.applications.application import Application
|
||||||
@@ -60,8 +65,11 @@ class WebBrowser(Application):
|
|||||||
:param: url: The address of the web page the browser requests
|
:param: url: The address of the web page the browser requests
|
||||||
:type: url: str
|
:type: url: str
|
||||||
"""
|
"""
|
||||||
|
if not self._can_perform_action():
|
||||||
|
return False
|
||||||
|
|
||||||
# reset latest response
|
# reset latest response
|
||||||
self.latest_response = None
|
self.latest_response = HttpResponsePacket(status_code=HttpStatusCode.NOT_FOUND)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parsed_url = urlparse(url)
|
parsed_url = urlparse(url)
|
||||||
@@ -91,11 +99,19 @@ class WebBrowser(Application):
|
|||||||
payload = HttpRequestPacket(request_method=HttpRequestMethod.GET, request_url=url)
|
payload = HttpRequestPacket(request_method=HttpRequestMethod.GET, request_url=url)
|
||||||
|
|
||||||
# send request
|
# send request
|
||||||
return self.send(
|
if self.send(
|
||||||
payload=payload,
|
payload=payload,
|
||||||
dest_ip_address=self.domain_name_ip_address,
|
dest_ip_address=self.domain_name_ip_address,
|
||||||
dest_port=parsed_url.port if parsed_url.port else Port.HTTP,
|
dest_port=parsed_url.port if parsed_url.port else Port.HTTP,
|
||||||
|
):
|
||||||
|
self.sys_log.info(
|
||||||
|
f"{self.name}: Received HTTP {payload.request_method.name} "
|
||||||
|
f"Response {payload.request_url} - {self.latest_response.status_code.value}"
|
||||||
)
|
)
|
||||||
|
return self.latest_response.status_code is HttpStatusCode.OK
|
||||||
|
else:
|
||||||
|
self.sys_log.error(f"Error sending Http Packet {str(payload)}")
|
||||||
|
return False
|
||||||
|
|
||||||
def send(
|
def send(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ class DatabaseService(Service):
|
|||||||
|
|
||||||
def backup_database(self) -> bool:
|
def backup_database(self) -> bool:
|
||||||
"""Create a backup of the database to the configured backup server."""
|
"""Create a backup of the database to the configured backup server."""
|
||||||
|
# check if this action can be performed
|
||||||
|
if not self._can_perform_action():
|
||||||
|
return False
|
||||||
|
|
||||||
# check if the backup server was configured
|
# check if the backup server was configured
|
||||||
if self.backup_server is None:
|
if self.backup_server is None:
|
||||||
self.sys_log.error(f"{self.name} - {self.sys_log.hostname}: not configured.")
|
self.sys_log.error(f"{self.name} - {self.sys_log.hostname}: not configured.")
|
||||||
@@ -73,6 +77,10 @@ class DatabaseService(Service):
|
|||||||
|
|
||||||
def restore_backup(self) -> bool:
|
def restore_backup(self) -> bool:
|
||||||
"""Restore a backup from backup server."""
|
"""Restore a backup from backup server."""
|
||||||
|
# check if this action can be performed
|
||||||
|
if not self._can_perform_action():
|
||||||
|
return False
|
||||||
|
|
||||||
software_manager: SoftwareManager = self.software_manager
|
software_manager: SoftwareManager = self.software_manager
|
||||||
ftp_client_service: FTPClient = software_manager.software["FTPClient"]
|
ftp_client_service: FTPClient = software_manager.software["FTPClient"]
|
||||||
|
|
||||||
@@ -173,6 +181,9 @@ class DatabaseService(Service):
|
|||||||
:param session_id: The session identifier.
|
:param session_id: The session identifier.
|
||||||
:return: True if the Status Code is 200, otherwise False.
|
:return: True if the Status Code is 200, otherwise False.
|
||||||
"""
|
"""
|
||||||
|
if not super().receive(payload=payload, session_id=session_id, **kwargs):
|
||||||
|
return False
|
||||||
|
|
||||||
result = {"status_code": 500, "data": []}
|
result = {"status_code": 500, "data": []}
|
||||||
if isinstance(payload, dict) and payload.get("type"):
|
if isinstance(payload, dict) and payload.get("type"):
|
||||||
if payload["type"] == "connect_request":
|
if payload["type"] == "connect_request":
|
||||||
|
|||||||
@@ -51,14 +51,18 @@ class DNSClient(Service):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def add_domain_to_cache(self, domain_name: str, ip_address: IPv4Address):
|
def add_domain_to_cache(self, domain_name: str, ip_address: IPv4Address) -> bool:
|
||||||
"""
|
"""
|
||||||
Adds a domain name to the DNS Client cache.
|
Adds a domain name to the DNS Client cache.
|
||||||
|
|
||||||
:param: domain_name: The domain name to save to cache
|
:param: domain_name: The domain name to save to cache
|
||||||
:param: ip_address: The IP Address to attach the domain name to
|
:param: ip_address: The IP Address to attach the domain name to
|
||||||
"""
|
"""
|
||||||
|
if not self._can_perform_action():
|
||||||
|
return False
|
||||||
|
|
||||||
self.dns_cache[domain_name] = ip_address
|
self.dns_cache[domain_name] = ip_address
|
||||||
|
return True
|
||||||
|
|
||||||
def check_domain_exists(
|
def check_domain_exists(
|
||||||
self,
|
self,
|
||||||
@@ -72,6 +76,9 @@ class DNSClient(Service):
|
|||||||
:param: session_id: The Session ID the payload is to originate from. Optional.
|
:param: session_id: The Session ID the payload is to originate from. Optional.
|
||||||
:param: is_reattempt: Checks if the request has been reattempted. Default is False.
|
:param: is_reattempt: Checks if the request has been reattempted. Default is False.
|
||||||
"""
|
"""
|
||||||
|
if not self._can_perform_action():
|
||||||
|
return False
|
||||||
|
|
||||||
# check if DNS server is configured
|
# check if DNS server is configured
|
||||||
if self.dns_server is None:
|
if self.dns_server is None:
|
||||||
self.sys_log.error(f"{self.name}: DNS Server is not configured")
|
self.sys_log.error(f"{self.name}: DNS Server is not configured")
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ class DNSServer(Service):
|
|||||||
:param target_domain: The single domain name requested by a DNS client.
|
:param target_domain: The single domain name requested by a DNS client.
|
||||||
:return ip_address: The IP address of that domain name or None.
|
:return ip_address: The IP address of that domain name or None.
|
||||||
"""
|
"""
|
||||||
|
if not self._can_perform_action():
|
||||||
|
return
|
||||||
|
|
||||||
return self.dns_table.get(target_domain)
|
return self.dns_table.get(target_domain)
|
||||||
|
|
||||||
def dns_register(self, domain_name: str, domain_ip_address: IPv4Address):
|
def dns_register(self, domain_name: str, domain_ip_address: IPv4Address):
|
||||||
@@ -60,6 +63,9 @@ class DNSServer(Service):
|
|||||||
:param: domain_ip_address: The IP address that the domain should route to
|
:param: domain_ip_address: The IP address that the domain should route to
|
||||||
:type: domain_ip_address: IPv4Address
|
:type: domain_ip_address: IPv4Address
|
||||||
"""
|
"""
|
||||||
|
if not self._can_perform_action():
|
||||||
|
return
|
||||||
|
|
||||||
self.dns_table[domain_name] = domain_ip_address
|
self.dns_table[domain_name] = domain_ip_address
|
||||||
|
|
||||||
def reset_component_for_episode(self, episode: int):
|
def reset_component_for_episode(self, episode: int):
|
||||||
@@ -88,10 +94,14 @@ class DNSServer(Service):
|
|||||||
|
|
||||||
:return: True if DNS request returns a valid IP, otherwise, False
|
:return: True if DNS request returns a valid IP, otherwise, False
|
||||||
"""
|
"""
|
||||||
|
if not super().receive(payload=payload, session_id=session_id, **kwargs):
|
||||||
|
return False
|
||||||
|
|
||||||
# The payload should be a DNS packet
|
# The payload should be a DNS packet
|
||||||
if not isinstance(payload, DNSPacket):
|
if not isinstance(payload, DNSPacket):
|
||||||
_LOGGER.debug(f"{payload} is not a DNSPacket")
|
_LOGGER.debug(f"{payload} is not a DNSPacket")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# cast payload into a DNS packet
|
# cast payload into a DNS packet
|
||||||
payload: DNSPacket = payload
|
payload: DNSPacket = payload
|
||||||
if payload.dns_request is not None:
|
if payload.dns_request is not None:
|
||||||
|
|||||||
@@ -72,10 +72,7 @@ class FTPClient(FTPServiceABC):
|
|||||||
|
|
||||||
# normally FTP will choose a random port for the transfer, but using the FTP command port will do for now
|
# normally FTP will choose a random port for the transfer, but using the FTP command port will do for now
|
||||||
# create FTP packet
|
# create FTP packet
|
||||||
payload: FTPPacket = FTPPacket(
|
payload: FTPPacket = FTPPacket(ftp_command=FTPCommand.PORT, ftp_command_args=Port.FTP)
|
||||||
ftp_command=FTPCommand.PORT,
|
|
||||||
ftp_command_args=Port.FTP,
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.send(payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id):
|
if self.send(payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id):
|
||||||
if payload.status_code == FTPStatusCode.OK:
|
if payload.status_code == FTPStatusCode.OK:
|
||||||
@@ -271,7 +268,10 @@ class FTPClient(FTPServiceABC):
|
|||||||
the same node.
|
the same node.
|
||||||
"""
|
"""
|
||||||
if payload.status_code is None:
|
if payload.status_code is None:
|
||||||
|
self.sys_log.error(f"FTP Server could not be found - Error Code: {payload.status_code.value}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
self.sys_log.info(f"{self.name}: Received FTP Response {payload.ftp_command.name} {payload.status_code.value}")
|
||||||
|
|
||||||
self._process_ftp_command(payload=payload, session_id=session_id)
|
self._process_ftp_command(payload=payload, session_id=session_id)
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -86,6 +86,9 @@ class FTPServer(FTPServiceABC):
|
|||||||
prevents an FTP request loop - FTP client and servers can exist on
|
prevents an FTP request loop - FTP client and servers can exist on
|
||||||
the same node.
|
the same node.
|
||||||
"""
|
"""
|
||||||
|
if not super().receive(payload=payload, session_id=session_id, **kwargs):
|
||||||
|
return False
|
||||||
|
|
||||||
if payload.status_code is not None:
|
if payload.status_code is not None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -42,10 +42,13 @@ class DataManipulationBot(DatabaseClient):
|
|||||||
if self.server_ip_address and self.payload:
|
if self.server_ip_address and self.payload:
|
||||||
self.sys_log.info(f"{self.name}: Attempting to start the {self.name}")
|
self.sys_log.info(f"{self.name}: Attempting to start the {self.name}")
|
||||||
super().run()
|
super().run()
|
||||||
|
else:
|
||||||
|
self.sys_log.error(f"Failed to start the {self.name} as it requires both a target_ip_address and payload.")
|
||||||
|
|
||||||
|
def attack(self):
|
||||||
|
"""Run the data manipulation attack."""
|
||||||
if not self.connected:
|
if not self.connected:
|
||||||
self.connect()
|
self.connect()
|
||||||
if self.connected:
|
if self.connected:
|
||||||
self.query(self.payload)
|
self.query(self.payload)
|
||||||
self.sys_log.info(f"{self.name} payload delivered: {self.payload}")
|
self.sys_log.info(f"{self.name} payload delivered: {self.payload}")
|
||||||
else:
|
|
||||||
self.sys_log.error(f"Failed to start the {self.name} as it requires both a target_ip_address and payload.")
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from primaite import getLogger
|
from primaite import getLogger
|
||||||
from primaite.simulator.core import RequestManager, RequestType
|
from primaite.simulator.core import RequestManager, RequestType
|
||||||
@@ -40,6 +40,40 @@ class Service(IOSoftware):
|
|||||||
restart_countdown: Optional[int] = None
|
restart_countdown: Optional[int] = None
|
||||||
"If currently restarting, how many timesteps remain until the restart is finished."
|
"If currently restarting, how many timesteps remain until the restart is finished."
|
||||||
|
|
||||||
|
def _can_perform_action(self) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if the service can perform actions.
|
||||||
|
|
||||||
|
This is done by checking if the service is operating properly or the node it is installed
|
||||||
|
in is operational.
|
||||||
|
|
||||||
|
Returns true if the software can perform actions.
|
||||||
|
"""
|
||||||
|
if not super()._can_perform_action():
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.operating_state is not self.operating_state.RUNNING:
|
||||||
|
# service is not running
|
||||||
|
_LOGGER.error(f"Cannot perform action: {self.name} is {self.operating_state.name}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def receive(self, payload: Any, session_id: str, **kwargs) -> bool:
|
||||||
|
"""
|
||||||
|
Receives a payload from the SessionManager.
|
||||||
|
|
||||||
|
The specifics of how the payload is processed and whether a response payload
|
||||||
|
is generated should be implemented in subclasses.
|
||||||
|
|
||||||
|
|
||||||
|
:param payload: The payload to receive.
|
||||||
|
:param session_id: The identifier of the session that the payload is associated with.
|
||||||
|
:param kwargs: Additional keyword arguments specific to the implementation.
|
||||||
|
:return: True if the payload was successfully received and processed, False otherwise.
|
||||||
|
"""
|
||||||
|
return super().receive(payload=payload, session_id=session_id, **kwargs)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
@@ -91,6 +125,10 @@ class Service(IOSoftware):
|
|||||||
|
|
||||||
def start(self, **kwargs) -> None:
|
def start(self, **kwargs) -> None:
|
||||||
"""Start the service."""
|
"""Start the service."""
|
||||||
|
# cant start the service if the node it is on is off
|
||||||
|
if not super()._can_perform_action():
|
||||||
|
return
|
||||||
|
|
||||||
if self.operating_state == ServiceOperatingState.STOPPED:
|
if self.operating_state == ServiceOperatingState.STOPPED:
|
||||||
self.sys_log.info(f"Starting service {self.name}")
|
self.sys_log.info(f"Starting service {self.name}")
|
||||||
self.operating_state = ServiceOperatingState.RUNNING
|
self.operating_state = ServiceOperatingState.RUNNING
|
||||||
|
|||||||
@@ -155,6 +155,9 @@ class WebServer(Service):
|
|||||||
:param: payload: The payload to send.
|
:param: payload: The payload to send.
|
||||||
:param: session_id: The id of the session. Optional.
|
:param: session_id: The id of the session. Optional.
|
||||||
"""
|
"""
|
||||||
|
if not super().receive(payload=payload, session_id=session_id, **kwargs):
|
||||||
|
return False
|
||||||
|
|
||||||
# check if the payload is an HTTPPacket
|
# check if the payload is an HTTPPacket
|
||||||
if not isinstance(payload, HttpRequestPacket):
|
if not isinstance(payload, HttpRequestPacket):
|
||||||
self.sys_log.error("Payload is not an HTTPPacket")
|
self.sys_log.error("Payload is not an HTTPPacket")
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ from enum import Enum
|
|||||||
from ipaddress import IPv4Address
|
from ipaddress import IPv4Address
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from primaite.simulator.core import RequestManager, RequestType, SimComponent
|
from primaite.simulator.core import _LOGGER, RequestManager, RequestType, SimComponent
|
||||||
from primaite.simulator.file_system.file_system import FileSystem, Folder
|
from primaite.simulator.file_system.file_system import FileSystem, Folder
|
||||||
|
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||||
from primaite.simulator.network.transmission.transport_layer import Port
|
from primaite.simulator.network.transmission.transport_layer import Port
|
||||||
from primaite.simulator.system.core.session_manager import Session
|
from primaite.simulator.system.core.session_manager import Session
|
||||||
from primaite.simulator.system.core.sys_log import SysLog
|
from primaite.simulator.system.core.sys_log import SysLog
|
||||||
@@ -225,6 +226,21 @@ class IOSoftware(Software):
|
|||||||
)
|
)
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _can_perform_action(self) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if the software can perform actions.
|
||||||
|
|
||||||
|
This is done by checking if the software is operating properly or the node it is installed
|
||||||
|
in is operational.
|
||||||
|
|
||||||
|
Returns true if the software can perform actions.
|
||||||
|
"""
|
||||||
|
if self.software_manager and self.software_manager.node.operating_state is NodeOperatingState.OFF:
|
||||||
|
_LOGGER.debug(f"{self.name} Error: {self.software_manager.node.hostname} is not online.")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def send(
|
def send(
|
||||||
self,
|
self,
|
||||||
payload: Any,
|
payload: Any,
|
||||||
@@ -243,6 +259,9 @@ class IOSoftware(Software):
|
|||||||
|
|
||||||
:return: True if successful, False otherwise.
|
:return: True if successful, False otherwise.
|
||||||
"""
|
"""
|
||||||
|
if not self._can_perform_action():
|
||||||
|
return False
|
||||||
|
|
||||||
return self.software_manager.send_payload_to_session_manager(
|
return self.software_manager.send_payload_to_session_manager(
|
||||||
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id
|
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id
|
||||||
)
|
)
|
||||||
@@ -261,4 +280,5 @@ class IOSoftware(Software):
|
|||||||
:param kwargs: Additional keyword arguments specific to the implementation.
|
:param kwargs: Additional keyword arguments specific to the implementation.
|
||||||
:return: True if the payload was successfully received and processed, False otherwise.
|
:return: True if the payload was successfully received and processed, False otherwise.
|
||||||
"""
|
"""
|
||||||
pass
|
# return false if not allowed to perform actions
|
||||||
|
return self._can_perform_action()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from primaite.session.session import PrimaiteSession
|
|||||||
# from primaite.primaite_session import PrimaiteSession
|
# from primaite.primaite_session import PrimaiteSession
|
||||||
from primaite.simulator.network.container import Network
|
from primaite.simulator.network.container import Network
|
||||||
from primaite.simulator.network.networks import arcd_uc2_network
|
from primaite.simulator.network.networks import arcd_uc2_network
|
||||||
|
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
|
||||||
from primaite.simulator.system.applications.application import Application
|
from primaite.simulator.system.applications.application import Application
|
||||||
from primaite.simulator.system.core.sys_log import SysLog
|
from primaite.simulator.system.core.sys_log import SysLog
|
||||||
@@ -34,6 +35,12 @@ from primaite.simulator.network.hardware.base import Node
|
|||||||
class TestService(Service):
|
class TestService(Service):
|
||||||
"""Test Service class"""
|
"""Test Service class"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
kwargs["name"] = "TestService"
|
||||||
|
kwargs["port"] = Port.HTTP
|
||||||
|
kwargs["protocol"] = IPProtocol.TCP
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def receive(self, payload: Any, session_id: str, **kwargs) -> bool:
|
def receive(self, payload: Any, session_id: str, **kwargs) -> bool:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -41,6 +48,12 @@ class TestService(Service):
|
|||||||
class TestApplication(Application):
|
class TestApplication(Application):
|
||||||
"""Test Application class"""
|
"""Test Application class"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
kwargs["name"] = "TestApplication"
|
||||||
|
kwargs["port"] = Port.HTTP
|
||||||
|
kwargs["protocol"] = IPProtocol.TCP
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def describe_state(self) -> Dict:
|
def describe_state(self) -> Dict:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -57,6 +70,11 @@ def service(file_system) -> TestService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def service_class():
|
||||||
|
return TestService
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def application(file_system) -> TestApplication:
|
def application(file_system) -> TestApplication:
|
||||||
return TestApplication(
|
return TestApplication(
|
||||||
@@ -64,6 +82,11 @@ def application(file_system) -> TestApplication:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def application_class():
|
||||||
|
return TestApplication
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def file_system() -> FileSystem:
|
def file_system() -> FileSystem:
|
||||||
return Node(hostname="fs_node").file_system
|
return Node(hostname="fs_node").file_system
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ def test_data_manipulation(uc2_network):
|
|||||||
|
|
||||||
# Now we run the DataManipulationBot
|
# Now we run the DataManipulationBot
|
||||||
db_manipulation_bot.run()
|
db_manipulation_bot.run()
|
||||||
|
db_manipulation_bot.attack()
|
||||||
|
|
||||||
# Now check that the DB client on the web_server cannot query the users table on the database
|
# Now check that the DB client on the web_server cannot query the users table on the database
|
||||||
assert not db_client.query("SELECT")
|
assert not db_client.query("SELECT")
|
||||||
|
|||||||
121
tests/integration_tests/system/test_application_on_node.py
Normal file
121
tests/integration_tests/system/test_application_on_node.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||||
|
from primaite.simulator.network.hardware.nodes.computer import Computer
|
||||||
|
from primaite.simulator.system.applications.application import Application, ApplicationOperatingState
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def populated_node(application_class) -> Tuple[Application, Computer]:
|
||||||
|
computer: Computer = Computer(
|
||||||
|
hostname="test_computer",
|
||||||
|
ip_address="192.168.1.2",
|
||||||
|
subnet_mask="255.255.255.0",
|
||||||
|
default_gateway="192.168.1.1",
|
||||||
|
operating_state=NodeOperatingState.ON,
|
||||||
|
)
|
||||||
|
computer.software_manager.install(application_class)
|
||||||
|
|
||||||
|
app = computer.software_manager.software["TestApplication"]
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
return app, computer
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_on_offline_node(application_class):
|
||||||
|
"""Test to check that the service cannot be interacted with when node it is on is off."""
|
||||||
|
computer: Computer = Computer(
|
||||||
|
hostname="test_computer",
|
||||||
|
ip_address="192.168.1.2",
|
||||||
|
subnet_mask="255.255.255.0",
|
||||||
|
default_gateway="192.168.1.1",
|
||||||
|
operating_state=NodeOperatingState.ON,
|
||||||
|
)
|
||||||
|
computer.software_manager.install(application_class)
|
||||||
|
|
||||||
|
app: Application = computer.software_manager.software["TestApplication"]
|
||||||
|
|
||||||
|
computer.power_off()
|
||||||
|
|
||||||
|
for i in range(computer.shut_down_duration + 1):
|
||||||
|
computer.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
assert computer.operating_state is NodeOperatingState.OFF
|
||||||
|
assert app.operating_state is ApplicationOperatingState.CLOSED
|
||||||
|
|
||||||
|
app.run()
|
||||||
|
assert app.operating_state is ApplicationOperatingState.CLOSED
|
||||||
|
|
||||||
|
|
||||||
|
def test_server_turns_off_service(populated_node):
|
||||||
|
"""Check that the service is turned off when the server is turned off"""
|
||||||
|
app, computer = populated_node
|
||||||
|
|
||||||
|
assert computer.operating_state is NodeOperatingState.ON
|
||||||
|
assert app.operating_state is ApplicationOperatingState.RUNNING
|
||||||
|
|
||||||
|
computer.power_off()
|
||||||
|
|
||||||
|
for i in range(computer.shut_down_duration + 1):
|
||||||
|
computer.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
assert computer.operating_state is NodeOperatingState.OFF
|
||||||
|
assert app.operating_state is ApplicationOperatingState.CLOSED
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_cannot_be_turned_on_when_server_is_off(populated_node):
|
||||||
|
"""Check that the service cannot be started when the server is off."""
|
||||||
|
app, computer = populated_node
|
||||||
|
|
||||||
|
assert computer.operating_state is NodeOperatingState.ON
|
||||||
|
assert app.operating_state is ApplicationOperatingState.RUNNING
|
||||||
|
|
||||||
|
computer.power_off()
|
||||||
|
|
||||||
|
for i in range(computer.shut_down_duration + 1):
|
||||||
|
computer.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
assert computer.operating_state is NodeOperatingState.OFF
|
||||||
|
assert app.operating_state is ApplicationOperatingState.CLOSED
|
||||||
|
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
assert computer.operating_state is NodeOperatingState.OFF
|
||||||
|
assert app.operating_state is ApplicationOperatingState.CLOSED
|
||||||
|
|
||||||
|
|
||||||
|
def test_server_turns_on_service(populated_node):
|
||||||
|
"""Check that turning on the server turns on service."""
|
||||||
|
app, computer = populated_node
|
||||||
|
|
||||||
|
assert computer.operating_state is NodeOperatingState.ON
|
||||||
|
assert app.operating_state is ApplicationOperatingState.RUNNING
|
||||||
|
|
||||||
|
computer.power_off()
|
||||||
|
|
||||||
|
for i in range(computer.shut_down_duration + 1):
|
||||||
|
computer.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
assert computer.operating_state is NodeOperatingState.OFF
|
||||||
|
assert app.operating_state is ApplicationOperatingState.CLOSED
|
||||||
|
|
||||||
|
computer.power_on()
|
||||||
|
|
||||||
|
for i in range(computer.start_up_duration + 1):
|
||||||
|
computer.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
assert computer.operating_state is NodeOperatingState.ON
|
||||||
|
assert app.operating_state is ApplicationOperatingState.RUNNING
|
||||||
|
|
||||||
|
computer.start_up_duration = 0
|
||||||
|
computer.shut_down_duration = 0
|
||||||
|
|
||||||
|
computer.power_off()
|
||||||
|
assert computer.operating_state is NodeOperatingState.OFF
|
||||||
|
assert app.operating_state is ApplicationOperatingState.CLOSED
|
||||||
|
|
||||||
|
computer.power_on()
|
||||||
|
assert computer.operating_state is NodeOperatingState.ON
|
||||||
|
assert app.operating_state is ApplicationOperatingState.RUNNING
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
from ipaddress import IPv4Address
|
from ipaddress import IPv4Address
|
||||||
|
|
||||||
|
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||||
from primaite.simulator.network.hardware.nodes.server import Server
|
from primaite.simulator.network.hardware.nodes.server import Server
|
||||||
from primaite.simulator.system.applications.database_client import DatabaseClient
|
from primaite.simulator.system.applications.database_client import DatabaseClient
|
||||||
from primaite.simulator.system.services.database.database_service import DatabaseService
|
from primaite.simulator.system.services.database.database_service import DatabaseService
|
||||||
from primaite.simulator.system.services.ftp.ftp_server import FTPServer
|
from primaite.simulator.system.services.ftp.ftp_server import FTPServer
|
||||||
|
from primaite.simulator.system.services.service import ServiceOperatingState
|
||||||
|
|
||||||
|
|
||||||
def test_database_client_server_connection(uc2_network):
|
def test_database_client_server_connection(uc2_network):
|
||||||
@@ -55,7 +57,8 @@ def test_database_client_query(uc2_network):
|
|||||||
"""Tests DB query across the network returns HTTP status 200 and date."""
|
"""Tests DB query across the network returns HTTP status 200 and date."""
|
||||||
web_server: Server = uc2_network.get_node_by_hostname("web_server")
|
web_server: Server = uc2_network.get_node_by_hostname("web_server")
|
||||||
db_client: DatabaseClient = web_server.software_manager.software["DatabaseClient"]
|
db_client: DatabaseClient = web_server.software_manager.software["DatabaseClient"]
|
||||||
db_client.connect()
|
|
||||||
|
assert db_client.connected
|
||||||
|
|
||||||
assert db_client.query("SELECT")
|
assert db_client.query("SELECT")
|
||||||
|
|
||||||
@@ -92,3 +95,28 @@ def test_restore_backup(uc2_network):
|
|||||||
assert db_service.restore_backup() is True
|
assert db_service.restore_backup() is True
|
||||||
|
|
||||||
assert db_service.file_system.get_file(folder_name="database", file_name="database.db") is not None
|
assert db_service.file_system.get_file(folder_name="database", file_name="database.db") is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_database_client_cannot_query_offline_database_server(uc2_network):
|
||||||
|
"""Tests DB query across the network returns HTTP status 404 when db server is offline."""
|
||||||
|
db_server: Server = uc2_network.get_node_by_hostname("database_server")
|
||||||
|
db_service: DatabaseService = db_server.software_manager.software["DatabaseService"]
|
||||||
|
|
||||||
|
assert db_server.operating_state is NodeOperatingState.ON
|
||||||
|
assert db_service.operating_state is ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
web_server: Server = uc2_network.get_node_by_hostname("web_server")
|
||||||
|
db_client: DatabaseClient = web_server.software_manager.software["DatabaseClient"]
|
||||||
|
assert db_client.connected
|
||||||
|
|
||||||
|
assert db_client.query("SELECT") is True
|
||||||
|
|
||||||
|
db_server.power_off()
|
||||||
|
|
||||||
|
for i in range(db_server.shut_down_duration + 1):
|
||||||
|
uc2_network.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
assert db_server.operating_state is NodeOperatingState.OFF
|
||||||
|
assert db_service.operating_state is ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
assert db_client.query("SELECT") is False
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from primaite.simulator.network.hardware.node_operating_state import 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.system.services.dns.dns_client import DNSClient
|
from primaite.simulator.system.services.dns.dns_client import DNSClient
|
||||||
@@ -24,3 +25,39 @@ def test_dns_client_server(uc2_network):
|
|||||||
# arcd.com is registered in dns server and should be saved to cache
|
# arcd.com is registered in dns server and should be saved to cache
|
||||||
assert dns_client.check_domain_exists(target_domain="arcd.com")
|
assert dns_client.check_domain_exists(target_domain="arcd.com")
|
||||||
assert dns_client.dns_cache.get("arcd.com", None) is not None
|
assert dns_client.dns_cache.get("arcd.com", None) is not None
|
||||||
|
|
||||||
|
assert len(dns_client.dns_cache) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_dns_client_requests_offline_dns_server(uc2_network):
|
||||||
|
client_1: Computer = uc2_network.get_node_by_hostname("client_1")
|
||||||
|
domain_controller: Server = uc2_network.get_node_by_hostname("domain_controller")
|
||||||
|
|
||||||
|
dns_client: DNSClient = client_1.software_manager.software["DNSClient"]
|
||||||
|
dns_server: DNSServer = domain_controller.software_manager.software["DNSServer"]
|
||||||
|
|
||||||
|
assert dns_client.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
assert dns_server.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
dns_server.show()
|
||||||
|
|
||||||
|
# arcd.com is registered in dns server
|
||||||
|
assert dns_client.check_domain_exists(target_domain="arcd.com")
|
||||||
|
assert dns_client.dns_cache.get("arcd.com", None) is not None
|
||||||
|
|
||||||
|
assert len(dns_client.dns_cache) == 1
|
||||||
|
dns_client.dns_cache = {}
|
||||||
|
|
||||||
|
domain_controller.power_off()
|
||||||
|
|
||||||
|
for i in range(domain_controller.shut_down_duration + 1):
|
||||||
|
uc2_network.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
assert domain_controller.operating_state == NodeOperatingState.OFF
|
||||||
|
assert dns_server.operating_state == ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
# this time it should not cache because dns server is not online
|
||||||
|
assert dns_client.check_domain_exists(target_domain="arcd.com") is False
|
||||||
|
assert dns_client.dns_cache.get("arcd.com", None) is None
|
||||||
|
|
||||||
|
assert len(dns_client.dns_cache) == 0
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ def test_ftp_client_store_file_in_server(uc2_network):
|
|||||||
backup_server: Server = uc2_network.get_node_by_hostname("backup_server")
|
backup_server: Server = uc2_network.get_node_by_hostname("backup_server")
|
||||||
|
|
||||||
ftp_client: FTPClient = client_1.software_manager.software["FTPClient"]
|
ftp_client: FTPClient = client_1.software_manager.software["FTPClient"]
|
||||||
ftp_server: FTPServer = backup_server.software_manager.software["FTPServer"]
|
ftp_server_service: FTPServer = backup_server.software_manager.software["FTPServer"]
|
||||||
|
|
||||||
assert ftp_client.operating_state == ServiceOperatingState.RUNNING
|
assert ftp_client.operating_state == ServiceOperatingState.RUNNING
|
||||||
assert ftp_server.operating_state == ServiceOperatingState.RUNNING
|
assert ftp_server_service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
# create file on ftp client
|
# create file on ftp client
|
||||||
ftp_client.file_system.create_file(file_name="test_file.txt")
|
ftp_client.file_system.create_file(file_name="test_file.txt")
|
||||||
@@ -31,7 +31,7 @@ def test_ftp_client_store_file_in_server(uc2_network):
|
|||||||
dest_ip_address=backup_server.nics.get(next(iter(backup_server.nics))).ip_address,
|
dest_ip_address=backup_server.nics.get(next(iter(backup_server.nics))).ip_address,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert ftp_server.file_system.get_file(folder_name="client_1_backup", file_name="test_file.txt")
|
assert ftp_server_service.file_system.get_file(folder_name="client_1_backup", file_name="test_file.txt")
|
||||||
|
|
||||||
|
|
||||||
def test_ftp_client_retrieve_file_from_server(uc2_network):
|
def test_ftp_client_retrieve_file_from_server(uc2_network):
|
||||||
@@ -42,13 +42,13 @@ def test_ftp_client_retrieve_file_from_server(uc2_network):
|
|||||||
backup_server: Server = uc2_network.get_node_by_hostname("backup_server")
|
backup_server: Server = uc2_network.get_node_by_hostname("backup_server")
|
||||||
|
|
||||||
ftp_client: FTPClient = client_1.software_manager.software["FTPClient"]
|
ftp_client: FTPClient = client_1.software_manager.software["FTPClient"]
|
||||||
ftp_server: FTPServer = backup_server.software_manager.software["FTPServer"]
|
ftp_server_service: FTPServer = backup_server.software_manager.software["FTPServer"]
|
||||||
|
|
||||||
assert ftp_client.operating_state == ServiceOperatingState.RUNNING
|
assert ftp_client.operating_state == ServiceOperatingState.RUNNING
|
||||||
assert ftp_server.operating_state == ServiceOperatingState.RUNNING
|
assert ftp_server_service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
# create file on ftp server
|
# create file on ftp server
|
||||||
ftp_server.file_system.create_file(file_name="test_file.txt", folder_name="file_share")
|
ftp_server_service.file_system.create_file(file_name="test_file.txt", folder_name="file_share")
|
||||||
|
|
||||||
assert ftp_client.request_file(
|
assert ftp_client.request_file(
|
||||||
src_folder_name="file_share",
|
src_folder_name="file_share",
|
||||||
@@ -60,3 +60,40 @@ def test_ftp_client_retrieve_file_from_server(uc2_network):
|
|||||||
|
|
||||||
# client should have retrieved the file
|
# client should have retrieved the file
|
||||||
assert ftp_client.file_system.get_file(folder_name="downloads", file_name="test_file.txt")
|
assert ftp_client.file_system.get_file(folder_name="downloads", file_name="test_file.txt")
|
||||||
|
|
||||||
|
|
||||||
|
def test_ftp_client_tries_to_connect_to_offline_server(uc2_network):
|
||||||
|
"""Test checks to make sure that the client can't do anything when the server is offline."""
|
||||||
|
client_1: Computer = uc2_network.get_node_by_hostname("client_1")
|
||||||
|
backup_server: Server = uc2_network.get_node_by_hostname("backup_server")
|
||||||
|
|
||||||
|
ftp_client: FTPClient = client_1.software_manager.software["FTPClient"]
|
||||||
|
ftp_server_service: FTPServer = backup_server.software_manager.software["FTPServer"]
|
||||||
|
|
||||||
|
assert ftp_client.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
assert ftp_server_service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
# create file on ftp server
|
||||||
|
ftp_server_service.file_system.create_file(file_name="test_file.txt", folder_name="file_share")
|
||||||
|
|
||||||
|
backup_server.power_off()
|
||||||
|
|
||||||
|
for i in range(backup_server.shut_down_duration + 1):
|
||||||
|
uc2_network.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
assert ftp_client.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
assert ftp_server_service.operating_state == ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
assert (
|
||||||
|
ftp_client.request_file(
|
||||||
|
src_folder_name="file_share",
|
||||||
|
src_file_name="test_file.txt",
|
||||||
|
dest_folder_name="downloads",
|
||||||
|
dest_file_name="test_file.txt",
|
||||||
|
dest_ip_address=backup_server.nics.get(next(iter(backup_server.nics))).ip_address,
|
||||||
|
)
|
||||||
|
is False
|
||||||
|
)
|
||||||
|
|
||||||
|
# client should have retrieved the file
|
||||||
|
assert ftp_client.file_system.get_file(folder_name="downloads", file_name="test_file.txt") is None
|
||||||
|
|||||||
129
tests/integration_tests/system/test_service_on_node.py
Normal file
129
tests/integration_tests/system/test_service_on_node.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||||
|
from primaite.simulator.network.hardware.nodes.computer import Computer
|
||||||
|
from primaite.simulator.network.hardware.nodes.server import Server
|
||||||
|
from primaite.simulator.system.services.service import Service, ServiceOperatingState
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def populated_node(
|
||||||
|
service_class,
|
||||||
|
) -> Tuple[Server, Service]:
|
||||||
|
server = Server(
|
||||||
|
hostname="server", ip_address="192.168.0.1", subnet_mask="255.255.255.0", operating_state=NodeOperatingState.ON
|
||||||
|
)
|
||||||
|
server.software_manager.install(service_class)
|
||||||
|
|
||||||
|
service = server.software_manager.software["TestService"]
|
||||||
|
service.start()
|
||||||
|
|
||||||
|
return server, service
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_on_offline_node(service_class):
|
||||||
|
"""Test to check that the service cannot be interacted with when node it is on is off."""
|
||||||
|
computer: Computer = Computer(
|
||||||
|
hostname="test_computer",
|
||||||
|
ip_address="192.168.1.2",
|
||||||
|
subnet_mask="255.255.255.0",
|
||||||
|
default_gateway="192.168.1.1",
|
||||||
|
operating_state=NodeOperatingState.ON,
|
||||||
|
)
|
||||||
|
computer.software_manager.install(service_class)
|
||||||
|
|
||||||
|
service: Service = computer.software_manager.software["TestService"]
|
||||||
|
|
||||||
|
computer.power_off()
|
||||||
|
|
||||||
|
for i in range(computer.shut_down_duration + 1):
|
||||||
|
computer.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
assert computer.operating_state is NodeOperatingState.OFF
|
||||||
|
assert service.operating_state is ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
service.start()
|
||||||
|
assert service.operating_state is ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
service.resume()
|
||||||
|
assert service.operating_state is ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
service.restart()
|
||||||
|
assert service.operating_state is ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
service.pause()
|
||||||
|
assert service.operating_state is ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
|
||||||
|
def test_server_turns_off_service(populated_node):
|
||||||
|
"""Check that the service is turned off when the server is turned off"""
|
||||||
|
server, service = populated_node
|
||||||
|
|
||||||
|
assert server.operating_state is NodeOperatingState.ON
|
||||||
|
assert service.operating_state is ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
server.power_off()
|
||||||
|
|
||||||
|
for i in range(server.shut_down_duration + 1):
|
||||||
|
server.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
assert server.operating_state is NodeOperatingState.OFF
|
||||||
|
assert service.operating_state is ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_cannot_be_turned_on_when_server_is_off(populated_node):
|
||||||
|
"""Check that the service cannot be started when the server is off."""
|
||||||
|
server, service = populated_node
|
||||||
|
|
||||||
|
assert server.operating_state is NodeOperatingState.ON
|
||||||
|
assert service.operating_state is ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
server.power_off()
|
||||||
|
|
||||||
|
for i in range(server.shut_down_duration + 1):
|
||||||
|
server.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
assert server.operating_state is NodeOperatingState.OFF
|
||||||
|
assert service.operating_state is ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
service.start()
|
||||||
|
|
||||||
|
assert server.operating_state is NodeOperatingState.OFF
|
||||||
|
assert service.operating_state is ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
|
||||||
|
def test_server_turns_on_service(populated_node):
|
||||||
|
"""Check that turning on the server turns on service."""
|
||||||
|
server, service = populated_node
|
||||||
|
|
||||||
|
assert server.operating_state is NodeOperatingState.ON
|
||||||
|
assert service.operating_state is ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
server.power_off()
|
||||||
|
|
||||||
|
for i in range(server.shut_down_duration + 1):
|
||||||
|
server.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
assert server.operating_state is NodeOperatingState.OFF
|
||||||
|
assert service.operating_state is ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
server.power_on()
|
||||||
|
|
||||||
|
for i in range(server.start_up_duration + 1):
|
||||||
|
server.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
assert server.operating_state is NodeOperatingState.ON
|
||||||
|
assert service.operating_state is ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
server.start_up_duration = 0
|
||||||
|
server.shut_down_duration = 0
|
||||||
|
|
||||||
|
server.power_off()
|
||||||
|
assert server.operating_state is NodeOperatingState.OFF
|
||||||
|
assert service.operating_state is ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
server.power_on()
|
||||||
|
assert server.operating_state is NodeOperatingState.ON
|
||||||
|
assert service.operating_state is ServiceOperatingState.RUNNING
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from primaite.simulator.network.hardware.node_operating_state import 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.protocols.http import HttpStatusCode
|
from primaite.simulator.network.protocols.http import HttpStatusCode
|
||||||
@@ -47,6 +48,55 @@ def test_web_page_get_users_page_request_with_ip_address(uc2_network):
|
|||||||
|
|
||||||
assert web_client.get_webpage(f"http://{web_server_ip}/users/") is True
|
assert web_client.get_webpage(f"http://{web_server_ip}/users/") is True
|
||||||
|
|
||||||
# latest reponse should have status code 200
|
# latest response should have status code 200
|
||||||
assert web_client.latest_response is not None
|
assert web_client.latest_response is not None
|
||||||
assert web_client.latest_response.status_code == HttpStatusCode.OK
|
assert web_client.latest_response.status_code == HttpStatusCode.OK
|
||||||
|
|
||||||
|
|
||||||
|
def test_web_page_request_from_shut_down_server(uc2_network):
|
||||||
|
"""Test to see that the web server does not respond when the server is off."""
|
||||||
|
client_1: Computer = uc2_network.get_node_by_hostname("client_1")
|
||||||
|
web_client: WebBrowser = client_1.software_manager.software["WebBrowser"]
|
||||||
|
web_client.run()
|
||||||
|
|
||||||
|
web_server: Server = uc2_network.get_node_by_hostname("web_server")
|
||||||
|
|
||||||
|
assert web_client.operating_state == ApplicationOperatingState.RUNNING
|
||||||
|
|
||||||
|
assert web_client.get_webpage("http://arcd.com/users/") is True
|
||||||
|
|
||||||
|
# latest response should have status code 200
|
||||||
|
assert web_client.latest_response.status_code == HttpStatusCode.OK
|
||||||
|
|
||||||
|
web_server.power_off()
|
||||||
|
|
||||||
|
for i in range(web_server.shut_down_duration + 1):
|
||||||
|
uc2_network.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
# node should be off
|
||||||
|
assert web_server.operating_state is NodeOperatingState.OFF
|
||||||
|
|
||||||
|
assert web_client.get_webpage("http://arcd.com/users/") is False
|
||||||
|
assert web_client.latest_response.status_code == HttpStatusCode.NOT_FOUND
|
||||||
|
|
||||||
|
|
||||||
|
def test_web_page_request_from_closed_web_browser(uc2_network):
|
||||||
|
client_1: Computer = uc2_network.get_node_by_hostname("client_1")
|
||||||
|
web_client: WebBrowser = client_1.software_manager.software["WebBrowser"]
|
||||||
|
web_client.run()
|
||||||
|
|
||||||
|
web_server: Server = uc2_network.get_node_by_hostname("web_server")
|
||||||
|
|
||||||
|
assert web_client.operating_state == ApplicationOperatingState.RUNNING
|
||||||
|
|
||||||
|
assert web_client.get_webpage("http://arcd.com/users/") is True
|
||||||
|
|
||||||
|
# latest response should have status code 200
|
||||||
|
assert web_client.latest_response.status_code == HttpStatusCode.OK
|
||||||
|
|
||||||
|
web_client.close()
|
||||||
|
|
||||||
|
# node should be off
|
||||||
|
assert web_client.operating_state is ApplicationOperatingState.CLOSED
|
||||||
|
|
||||||
|
assert web_client.get_webpage("http://arcd.com/users/") is False
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from ipaddress import IPv4Address
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from primaite.simulator.network.hardware.base import Node
|
from primaite.simulator.network.hardware.base import Node
|
||||||
|
from primaite.simulator.network.hardware.node_operating_state import 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.protocols.dns import DNSPacket, DNSReply, DNSRequest
|
from primaite.simulator.network.protocols.dns import DNSPacket, DNSReply, DNSRequest
|
||||||
@@ -10,15 +11,19 @@ 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
|
||||||
from primaite.simulator.system.services.dns.dns_client import DNSClient
|
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.dns.dns_server import DNSServer
|
||||||
|
from primaite.simulator.system.services.service import ServiceOperatingState
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def dns_server() -> Node:
|
def dns_server() -> Node:
|
||||||
node = Server(
|
node = Server(
|
||||||
hostname="dns_server", ip_address="192.168.1.10", subnet_mask="255.255.255.0", default_gateway="192.168.1.1"
|
hostname="dns_server",
|
||||||
|
ip_address="192.168.1.10",
|
||||||
|
subnet_mask="255.255.255.0",
|
||||||
|
default_gateway="192.168.1.1",
|
||||||
|
operating_state=NodeOperatingState.ON,
|
||||||
)
|
)
|
||||||
node.software_manager.install(software_class=DNSServer)
|
node.software_manager.install(software_class=DNSServer)
|
||||||
node.software_manager.software["DNSServer"].start()
|
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
@@ -50,6 +55,44 @@ def test_create_dns_client(dns_client):
|
|||||||
assert dns_client_service.protocol is IPProtocol.TCP
|
assert dns_client_service.protocol is IPProtocol.TCP
|
||||||
|
|
||||||
|
|
||||||
|
def test_dns_client_add_domain_to_cache_when_not_running(dns_client):
|
||||||
|
dns_client_service: DNSClient = dns_client.software_manager.software["DNSClient"]
|
||||||
|
assert dns_client.operating_state is NodeOperatingState.OFF
|
||||||
|
assert dns_client_service.operating_state is ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
assert (
|
||||||
|
dns_client_service.add_domain_to_cache(domain_name="test.com", ip_address=IPv4Address("192.168.1.100")) is False
|
||||||
|
)
|
||||||
|
|
||||||
|
assert dns_client_service.dns_cache.get("test.com") is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_dns_client_check_domain_exists_when_not_running(dns_client):
|
||||||
|
dns_client.operating_state = NodeOperatingState.ON
|
||||||
|
dns_client_service: DNSClient = dns_client.software_manager.software["DNSClient"]
|
||||||
|
dns_client_service.start()
|
||||||
|
|
||||||
|
assert dns_client.operating_state is NodeOperatingState.ON
|
||||||
|
assert dns_client_service.operating_state is ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
assert (
|
||||||
|
dns_client_service.add_domain_to_cache(domain_name="test.com", ip_address=IPv4Address("192.168.1.100"))
|
||||||
|
is not False
|
||||||
|
)
|
||||||
|
|
||||||
|
assert dns_client_service.check_domain_exists("test.com") is True
|
||||||
|
|
||||||
|
dns_client.power_off()
|
||||||
|
|
||||||
|
for i in range(dns_client.shut_down_duration + 1):
|
||||||
|
dns_client.apply_timestep(timestep=i)
|
||||||
|
|
||||||
|
assert dns_client.operating_state is NodeOperatingState.OFF
|
||||||
|
assert dns_client_service.operating_state is ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
assert dns_client_service.check_domain_exists("test.com") is False
|
||||||
|
|
||||||
|
|
||||||
def test_dns_server_domain_name_registration(dns_server):
|
def test_dns_server_domain_name_registration(dns_server):
|
||||||
"""Test to check if the domain name registration works."""
|
"""Test to check if the domain name registration works."""
|
||||||
dns_server_service: DNSServer = dns_server.software_manager.software["DNSServer"]
|
dns_server_service: DNSServer = dns_server.software_manager.software["DNSServer"]
|
||||||
@@ -64,7 +107,9 @@ def test_dns_server_domain_name_registration(dns_server):
|
|||||||
|
|
||||||
def test_dns_client_check_domain_in_cache(dns_client):
|
def test_dns_client_check_domain_in_cache(dns_client):
|
||||||
"""Test to make sure that the check_domain_in_cache returns the correct values."""
|
"""Test to make sure that the check_domain_in_cache returns the correct values."""
|
||||||
|
dns_client.operating_state = NodeOperatingState.ON
|
||||||
dns_client_service: DNSClient = dns_client.software_manager.software["DNSClient"]
|
dns_client_service: DNSClient = dns_client.software_manager.software["DNSClient"]
|
||||||
|
dns_client_service.start()
|
||||||
|
|
||||||
# add a domain to the dns client cache
|
# add a domain to the dns client cache
|
||||||
dns_client_service.add_domain_to_cache("real-domain.com", IPv4Address("192.168.1.12"))
|
dns_client_service.add_domain_to_cache("real-domain.com", IPv4Address("192.168.1.12"))
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from ipaddress import IPv4Address
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from primaite.simulator.network.hardware.base import Node
|
from primaite.simulator.network.hardware.base import Node
|
||||||
|
from primaite.simulator.network.hardware.node_operating_state import 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.protocols.ftp import FTPCommand, FTPPacket, FTPStatusCode
|
from primaite.simulator.network.protocols.ftp import FTPCommand, FTPPacket, FTPStatusCode
|
||||||
@@ -15,17 +16,24 @@ from primaite.simulator.system.services.ftp.ftp_server import FTPServer
|
|||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def ftp_server() -> Node:
|
def ftp_server() -> Node:
|
||||||
node = Server(
|
node = Server(
|
||||||
hostname="ftp_server", ip_address="192.168.1.10", subnet_mask="255.255.255.0", default_gateway="192.168.1.1"
|
hostname="ftp_server",
|
||||||
|
ip_address="192.168.1.10",
|
||||||
|
subnet_mask="255.255.255.0",
|
||||||
|
default_gateway="192.168.1.1",
|
||||||
|
operating_state=NodeOperatingState.ON,
|
||||||
)
|
)
|
||||||
node.software_manager.install(software_class=FTPServer)
|
node.software_manager.install(software_class=FTPServer)
|
||||||
node.software_manager.software["FTPServer"].start()
|
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def ftp_client() -> Node:
|
def ftp_client() -> Node:
|
||||||
node = Computer(
|
node = Computer(
|
||||||
hostname="ftp_client", ip_address="192.168.1.11", subnet_mask="255.255.255.0", default_gateway="192.168.1.1"
|
hostname="ftp_client",
|
||||||
|
ip_address="192.168.1.11",
|
||||||
|
subnet_mask="255.255.255.0",
|
||||||
|
default_gateway="192.168.1.1",
|
||||||
|
operating_state=NodeOperatingState.ON,
|
||||||
)
|
)
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user