#1706 - Got the code services, application, and process base classes stubbed out. Need them now so that I can leverage them for core node services required.
This commit is contained in:
@@ -64,7 +64,7 @@ class SimComponent(BaseModel):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def reset_component_for_episode(self):
|
def reset_component_for_episode(self, episode: int):
|
||||||
"""
|
"""
|
||||||
Reset this component to its original state for a new episode.
|
Reset this component to its original state for a new episode.
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ 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
|
||||||
from primaite.simulator.network.transmission.transport_layer import Port, TCPHeader
|
from primaite.simulator.network.transmission.transport_layer import Port, TCPHeader
|
||||||
from primaite.simulator.system.processes.pcap import PCAP
|
from primaite.simulator.system.packet_capture import PacketCapture
|
||||||
from primaite.simulator.system.processes.sys_log import SysLog
|
from primaite.simulator.system.sys_log import SysLog
|
||||||
|
|
||||||
_LOGGER = getLogger(__name__)
|
_LOGGER = getLogger(__name__)
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ class NIC(SimComponent):
|
|||||||
"The Link to which the NIC is connected."
|
"The Link to which the NIC is connected."
|
||||||
enabled: bool = False
|
enabled: bool = False
|
||||||
"Indicates whether the NIC is enabled."
|
"Indicates whether the NIC is enabled."
|
||||||
pcap: Optional[PCAP] = None
|
pcap: Optional[PacketCapture] = None
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -132,10 +132,10 @@ class NIC(SimComponent):
|
|||||||
"""Attempt to enable the NIC."""
|
"""Attempt to enable the NIC."""
|
||||||
if not self.enabled:
|
if not self.enabled:
|
||||||
if self.connected_node:
|
if self.connected_node:
|
||||||
if self.connected_node.hardware_state == NodeOperatingState.ON:
|
if self.connected_node.operating_state == NodeOperatingState.ON:
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
_LOGGER.info(f"NIC {self} enabled")
|
_LOGGER.info(f"NIC {self} enabled")
|
||||||
self.pcap = PCAP(hostname=self.connected_node.hostname, ip_address=self.ip_address)
|
self.pcap = PacketCapture(hostname=self.connected_node.hostname, ip_address=self.ip_address)
|
||||||
if self.connected_link:
|
if self.connected_link:
|
||||||
self.connected_link.endpoint_up()
|
self.connected_link.endpoint_up()
|
||||||
else:
|
else:
|
||||||
@@ -393,7 +393,7 @@ class Node(SimComponent):
|
|||||||
A basic Node class.
|
A basic Node class.
|
||||||
|
|
||||||
:param hostname: The node hostname on the network.
|
:param hostname: The node hostname on the network.
|
||||||
:param hardware_state: The hardware state of the node.
|
:param operating_state: The node operating state.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
hostname: str
|
hostname: str
|
||||||
|
|||||||
86
src/primaite/simulator/system/applications/application.py
Normal file
86
src/primaite/simulator/system/applications/application.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
from abc import abstractmethod
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Any, List, Dict, Set
|
||||||
|
|
||||||
|
from primaite.simulator.system.software import IOSoftware
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationOperatingState(Enum):
|
||||||
|
"""Enumeration of Application Operating States."""
|
||||||
|
|
||||||
|
CLOSED = 0
|
||||||
|
"The application is closed or not running."
|
||||||
|
RUNNING = 1
|
||||||
|
"The application is running."
|
||||||
|
INSTALLING = 3
|
||||||
|
"The application is being installed or updated."
|
||||||
|
|
||||||
|
|
||||||
|
class Application(IOSoftware):
|
||||||
|
"""
|
||||||
|
Represents an Application in the simulation environment.
|
||||||
|
|
||||||
|
Applications are user-facing programs that may perform input/output operations.
|
||||||
|
"""
|
||||||
|
operating_state: ApplicationOperatingState
|
||||||
|
"The current operating state of the Application."
|
||||||
|
execution_control_status: str
|
||||||
|
"Control status of the application's execution. It could be 'manual' or 'automatic'."
|
||||||
|
num_executions: int = 0
|
||||||
|
"The number of times the application has been executed. Default is 0."
|
||||||
|
groups: Set[str] = set()
|
||||||
|
"The set of groups to which the application belongs."
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def describe_state(self) -> Dict:
|
||||||
|
"""
|
||||||
|
Describes the current state of the software.
|
||||||
|
|
||||||
|
The specifics of the software's state, including its health, criticality,
|
||||||
|
and any other pertinent information, should be implemented in subclasses.
|
||||||
|
|
||||||
|
:return: A dictionary containing key-value pairs representing the current state of the software.
|
||||||
|
:rtype: Dict
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def apply_action(self, action: List[str]) -> None:
|
||||||
|
"""
|
||||||
|
Applies a list of actions to the Application.
|
||||||
|
|
||||||
|
:param action: A list of actions to apply.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def reset_component_for_episode(self, episode: int):
|
||||||
|
"""
|
||||||
|
Resets the Application component for a new episode.
|
||||||
|
|
||||||
|
This method ensures the Application is ready for a new episode, including resetting any
|
||||||
|
stateful properties or statistics, and clearing any message queues.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def send(self, payload: Any) -> bool:
|
||||||
|
"""
|
||||||
|
Sends a payload to 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 send.
|
||||||
|
:return: True if successful, False otherwise.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def receive(self, payload: Any) -> 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.
|
||||||
|
:return: True if successful, False otherwise.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
30
src/primaite/simulator/system/arp_cache.py
Normal file
30
src/primaite/simulator/system/arp_cache.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from ipaddress import IPv4Address
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class ARPCacheService(BaseModel):
|
||||||
|
def __init__(self, node):
|
||||||
|
super().__init__()
|
||||||
|
self.node = node
|
||||||
|
|
||||||
|
def _add_arp_cache_entry(self, ip_address: IPv4Address, mac_address: str, nic: NIC):
|
||||||
|
...
|
||||||
|
|
||||||
|
def _remove_arp_cache_entry(self, ip_address: IPv4Address):
|
||||||
|
...
|
||||||
|
|
||||||
|
def _get_arp_cache_mac_address(self, ip_address: IPv4Address) -> Optional[str]:
|
||||||
|
...
|
||||||
|
|
||||||
|
def _get_arp_cache_nic(self, ip_address: IPv4Address) -> Optional[NIC]:
|
||||||
|
...
|
||||||
|
|
||||||
|
def _clear_arp_cache(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def _send_arp_request(self, target_ip_address: Union[IPv4Address, str]):
|
||||||
|
...
|
||||||
|
|
||||||
|
def process_arp_packet(self, from_nic: NIC, arp_packet: ARPPacket):
|
||||||
|
...
|
||||||
@@ -8,24 +8,26 @@ class _JSONFilter(logging.Filter):
|
|||||||
return record.getMessage().startswith("{") and record.getMessage().endswith("}")
|
return record.getMessage().startswith("{") and record.getMessage().endswith("}")
|
||||||
|
|
||||||
|
|
||||||
class PCAP:
|
class PacketCapture:
|
||||||
"""
|
"""
|
||||||
A logger class for logging Frames as json strings.
|
Represents a PacketCapture component on a Node in the simulation environment.
|
||||||
|
|
||||||
This is essentially a PrimAITE simulated version of PCAP.
|
PacketCapture is a service that logs Frames as json strings; It's Wireshark for PrimAITE.
|
||||||
|
|
||||||
The PCAPs are logged to: <simulation output directory>/<hostname>/<hostname>_<ip address>_pcap.log
|
The PCAPs are logged to: <simulation output directory>/<hostname>/<hostname>_<ip address>_pcap.log
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, hostname: str, ip_address: str):
|
def __init__(self, hostname: str, ip_address: str):
|
||||||
"""
|
"""
|
||||||
Initialize the PCAP instance.
|
Initialize the PacketCapture process.
|
||||||
|
|
||||||
:param hostname: The hostname for which PCAP logs are being recorded.
|
:param hostname: The hostname for which PCAP logs are being recorded.
|
||||||
:param ip_address: The IP address associated with the PCAP logs.
|
:param ip_address: The IP address associated with the PCAP logs.
|
||||||
"""
|
"""
|
||||||
self.hostname = hostname
|
self.hostname: str = hostname
|
||||||
self.ip_address = str(ip_address)
|
"The hostname for which PCAP logs are being recorded."
|
||||||
|
self.ip_address: str = ip_address
|
||||||
|
"The IP address associated with the PCAP logs."
|
||||||
self._setup_logger()
|
self._setup_logger()
|
||||||
|
|
||||||
def _setup_logger(self):
|
def _setup_logger(self):
|
||||||
@@ -51,7 +53,7 @@ class PCAP:
|
|||||||
root.mkdir(exist_ok=True, parents=True)
|
root.mkdir(exist_ok=True, parents=True)
|
||||||
return root / f"{self.hostname}_{self.ip_address}_pcap.log"
|
return root / f"{self.hostname}_{self.ip_address}_pcap.log"
|
||||||
|
|
||||||
def capture(self, frame): # noqa Please don't make me, I'll have a circular import and cant use if TYPE_CHECKING ;(
|
def capture(self, frame): # noqa - I'll have a circular import and cant use if TYPE_CHECKING ;(
|
||||||
"""
|
"""
|
||||||
Capture a Frame and log it.
|
Capture a Frame and log it.
|
||||||
|
|
||||||
37
src/primaite/simulator/system/processes/process.py
Normal file
37
src/primaite/simulator/system/processes/process.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from abc import abstractmethod
|
||||||
|
from enum import Enum
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
|
||||||
|
from primaite.simulator.system.software import Software
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessOperatingState(Enum):
|
||||||
|
"""Enumeration of Process Operating States."""
|
||||||
|
|
||||||
|
RUNNING = 1
|
||||||
|
"The process is running."
|
||||||
|
PAUSED = 2
|
||||||
|
"The process is temporarily paused."
|
||||||
|
|
||||||
|
|
||||||
|
class Process(Software):
|
||||||
|
"""
|
||||||
|
Represents a Process, a program in execution, in the simulation environment.
|
||||||
|
|
||||||
|
Processes are executed by a Node and do not have the ability to performing input/output operations.
|
||||||
|
"""
|
||||||
|
operating_state: ProcessOperatingState
|
||||||
|
"The current operating state of the Process."
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def describe_state(self) -> Dict:
|
||||||
|
"""
|
||||||
|
Describes the current state of the software.
|
||||||
|
|
||||||
|
The specifics of the software's state, including its health, criticality,
|
||||||
|
and any other pertinent information, should be implemented in subclasses.
|
||||||
|
|
||||||
|
:return: A dictionary containing key-value pairs representing the current state of the software.
|
||||||
|
:rtype: Dict
|
||||||
|
"""
|
||||||
|
pass
|
||||||
87
src/primaite/simulator/system/services/service.py
Normal file
87
src/primaite/simulator/system/services/service.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
from abc import abstractmethod
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
from primaite.simulator.system.software import IOSoftware
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceOperatingState(Enum):
|
||||||
|
"""Enumeration of Service Operating States."""
|
||||||
|
|
||||||
|
STOPPED = 0
|
||||||
|
"The service is not running."
|
||||||
|
RUNNING = 1
|
||||||
|
"The service is currently running."
|
||||||
|
RESTARTING = 2
|
||||||
|
"The service is in the process of restarting."
|
||||||
|
INSTALLING = 3
|
||||||
|
"The service is being installed or updated."
|
||||||
|
PAUSED = 4
|
||||||
|
"The service is temporarily paused."
|
||||||
|
DISABLED = 5
|
||||||
|
"The service is disabled and cannot be started."
|
||||||
|
|
||||||
|
|
||||||
|
class Service(IOSoftware):
|
||||||
|
"""
|
||||||
|
Represents a Service in the simulation environment.
|
||||||
|
|
||||||
|
Services are programs that run in the background and may perform input/output operations.
|
||||||
|
"""
|
||||||
|
operating_state: ServiceOperatingState
|
||||||
|
"The current operating state of the Service."
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def describe_state(self) -> Dict:
|
||||||
|
"""
|
||||||
|
Describes the current state of the software.
|
||||||
|
|
||||||
|
The specifics of the software's state, including its health, criticality,
|
||||||
|
and any other pertinent information, should be implemented in subclasses.
|
||||||
|
|
||||||
|
:return: A dictionary containing key-value pairs representing the current state of the software.
|
||||||
|
:rtype: Dict
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def apply_action(self, action: List[str]) -> None:
|
||||||
|
"""
|
||||||
|
Applies a list of actions to the Service.
|
||||||
|
|
||||||
|
:param action: A list of actions to apply.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def reset_component_for_episode(self, episode: int):
|
||||||
|
"""
|
||||||
|
Resets the Service component for a new episode.
|
||||||
|
|
||||||
|
This method ensures the Service is ready for a new episode, including resetting any
|
||||||
|
stateful properties or statistics, and clearing any message queues.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def send(self, payload: Any) -> bool:
|
||||||
|
"""
|
||||||
|
Sends a payload to 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 send.
|
||||||
|
:return: True if successful, False otherwise.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def receive(self, payload: Any) -> 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.
|
||||||
|
:return: True if successful, False otherwise.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
|
from abc import abstractmethod
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from typing import Any, Dict, List, Set
|
||||||
|
|
||||||
from primaite.simulator.core import SimComponent
|
from primaite.simulator.core import SimComponent
|
||||||
|
from primaite.simulator.network.transmission.transport_layer import Port
|
||||||
|
|
||||||
|
|
||||||
class SoftwareHealthState(Enum):
|
class SoftwareHealthState(Enum):
|
||||||
@@ -16,43 +19,6 @@ class SoftwareHealthState(Enum):
|
|||||||
"The software is undergoing patching or updates."
|
"The software is undergoing patching or updates."
|
||||||
|
|
||||||
|
|
||||||
class ApplicationOperatingState(Enum):
|
|
||||||
"""Enumeration of Application Operating States."""
|
|
||||||
|
|
||||||
CLOSED = 0
|
|
||||||
"The application is closed or not running."
|
|
||||||
RUNNING = 1
|
|
||||||
"The application is running."
|
|
||||||
INSTALLING = 3
|
|
||||||
"The application is being installed or updated."
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceOperatingState(Enum):
|
|
||||||
"""Enumeration of Service Operating States."""
|
|
||||||
|
|
||||||
STOPPED = 0
|
|
||||||
"The service is not running."
|
|
||||||
RUNNING = 1
|
|
||||||
"The service is currently running."
|
|
||||||
RESTARTING = 2
|
|
||||||
"The service is in the process of restarting."
|
|
||||||
INSTALLING = 3
|
|
||||||
"The service is being installed or updated."
|
|
||||||
PAUSED = 4
|
|
||||||
"The service is temporarily paused."
|
|
||||||
DISABLED = 5
|
|
||||||
"The service is disabled and cannot be started."
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessOperatingState(Enum):
|
|
||||||
"""Enumeration of Process Operating States."""
|
|
||||||
|
|
||||||
RUNNING = 1
|
|
||||||
"The process is running."
|
|
||||||
PAUSED = 2
|
|
||||||
"The process is temporarily paused."
|
|
||||||
|
|
||||||
|
|
||||||
class SoftwareCriticality(Enum):
|
class SoftwareCriticality(Enum):
|
||||||
"""Enumeration of Software Criticality Levels."""
|
"""Enumeration of Software Criticality Levels."""
|
||||||
|
|
||||||
@@ -70,25 +36,101 @@ class SoftwareCriticality(Enum):
|
|||||||
|
|
||||||
class Software(SimComponent):
|
class Software(SimComponent):
|
||||||
"""
|
"""
|
||||||
Represents software information along with its health, criticality, and status.
|
A base class representing software in a simulator environment.
|
||||||
|
|
||||||
This class inherits from the Pydantic BaseModel and provides a structured way to store
|
This class is intended to be subclassed by specific types of software entities.
|
||||||
information about software entities.
|
It outlines the fundamental attributes and behaviors expected of any software in the simulation.
|
||||||
|
|
||||||
Attributes:
|
|
||||||
name (str): The name of the software.
|
|
||||||
health_state_actual (SoftwareHealthState): The actual health state of the software.
|
|
||||||
health_state_visible (SoftwareHealthState): The health state of the software visible to users.
|
|
||||||
criticality (SoftwareCriticality): The criticality level of the software.
|
|
||||||
patching_count (int, optional): The count of patches applied to the software. Default is 0.
|
|
||||||
scanning_count (int, optional): The count of times the software has been scanned. Default is 0.
|
|
||||||
revealed_to_red (bool, optional): Indicates if the software has been revealed to red team. Default is False.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
|
"The name of the software."
|
||||||
health_state_actual: SoftwareHealthState
|
health_state_actual: SoftwareHealthState
|
||||||
|
"The actual health state of the software."
|
||||||
health_state_visible: SoftwareHealthState
|
health_state_visible: SoftwareHealthState
|
||||||
|
"The health state of the software visible to the red agent."
|
||||||
criticality: SoftwareCriticality
|
criticality: SoftwareCriticality
|
||||||
|
"The criticality level of the software."
|
||||||
patching_count: int = 0
|
patching_count: int = 0
|
||||||
|
"The count of patches applied to the software, defaults to 0."
|
||||||
scanning_count: int = 0
|
scanning_count: int = 0
|
||||||
|
"The count of times the software has been scanned, defaults to 0."
|
||||||
revealed_to_red: bool = False
|
revealed_to_red: bool = False
|
||||||
|
"Indicates if the software has been revealed to red agent, defaults is False."
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def describe_state(self) -> Dict:
|
||||||
|
"""
|
||||||
|
Describes the current state of the software.
|
||||||
|
|
||||||
|
The specifics of the software's state, including its health, criticality,
|
||||||
|
and any other pertinent information, should be implemented in subclasses.
|
||||||
|
|
||||||
|
:return: A dictionary containing key-value pairs representing the current state of the software.
|
||||||
|
:rtype: Dict
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def apply_action(self, action: List[str]) -> None:
|
||||||
|
"""
|
||||||
|
Applies a list of actions to the software.
|
||||||
|
|
||||||
|
The specifics of how these actions are applied should be implemented in subclasses.
|
||||||
|
|
||||||
|
:param action: A list of actions to apply.
|
||||||
|
:type action: List[str]
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def reset_component_for_episode(self, episode: int):
|
||||||
|
"""
|
||||||
|
Resets the software component for a new episode.
|
||||||
|
|
||||||
|
This method should ensure the software is ready for a new episode, including resetting any
|
||||||
|
stateful properties or statistics, and clearing any message queues. The specifics of what constitutes a
|
||||||
|
"reset" should be implemented in subclasses.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class IOSoftware(Software):
|
||||||
|
"""
|
||||||
|
Represents software in a simulator environment that is capable of input/output operations.
|
||||||
|
|
||||||
|
This base class is meant to be sub-classed by Application and Service classes. It provides the blueprint for
|
||||||
|
Applications and Services that can receive payloads from a Node's SessionManager (corresponding to layer 5 in the
|
||||||
|
OSI Model), process them according to their internals, and send a response payload back to the SessionManager if
|
||||||
|
required.
|
||||||
|
"""
|
||||||
|
installing_count: int = 0
|
||||||
|
"The number of times the software has been installed. Default is 0."
|
||||||
|
max_sessions: int = 1
|
||||||
|
"The maximum number of sessions that the software can handle simultaneously. Default is 0."
|
||||||
|
tcp: bool = True
|
||||||
|
"Indicates if the software uses TCP protocol for communication. Default is True."
|
||||||
|
udp: bool = True
|
||||||
|
"Indicates if the software uses UDP protocol for communication. Default is True."
|
||||||
|
ports: Set[Port]
|
||||||
|
"The set of ports to which the software is connected."
|
||||||
|
|
||||||
|
def send(self, payload: Any) -> bool:
|
||||||
|
"""
|
||||||
|
Sends a payload to 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 send.
|
||||||
|
:return: True if successful, False otherwise.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def receive(self, payload: Any) -> 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.
|
||||||
|
:return: True if successful, False otherwise.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|||||||
@@ -4,28 +4,36 @@ from pathlib import Path
|
|||||||
|
|
||||||
class _NotJSONFilter(logging.Filter):
|
class _NotJSONFilter(logging.Filter):
|
||||||
def filter(self, record: logging.LogRecord) -> bool:
|
def filter(self, record: logging.LogRecord) -> bool:
|
||||||
"""Filter logs that do not start and end with '{' and '}'."""
|
"""
|
||||||
|
Determines if a log message does not start and end with '{' and '}' (i.e., it is not a JSON-like message).
|
||||||
|
|
||||||
|
:param record: LogRecord object containing all the information pertinent to the event being logged.
|
||||||
|
:return: True if log message is not JSON-like, False otherwise.
|
||||||
|
"""
|
||||||
return not record.getMessage().startswith("{") and not record.getMessage().endswith("}")
|
return not record.getMessage().startswith("{") and not record.getMessage().endswith("}")
|
||||||
|
|
||||||
|
|
||||||
class SysLog:
|
class SysLog:
|
||||||
"""
|
"""
|
||||||
A simple logger class for writing the sys logs of a Node.
|
A SysLog class is a simple logger dedicated to managing and writing system logs for a Node.
|
||||||
|
|
||||||
Logs are logged to: <simulation output directory>/<hostname>/<hostname>_sys.log
|
Each log message is written to a file located at: <simulation output directory>/<hostname>/<hostname>_sys.log
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, hostname: str):
|
def __init__(self, hostname: str):
|
||||||
"""
|
"""
|
||||||
Initialize the SysLog instance.
|
Constructs a SysLog instance for a given hostname.
|
||||||
|
|
||||||
:param hostname: The hostname for which logs are being recorded.
|
:param hostname: The hostname associated with the system logs being recorded.
|
||||||
"""
|
"""
|
||||||
self.hostname = hostname
|
self.hostname = hostname
|
||||||
self._setup_logger()
|
self._setup_logger()
|
||||||
|
|
||||||
def _setup_logger(self):
|
def _setup_logger(self):
|
||||||
"""Set up the logger configuration."""
|
"""
|
||||||
|
Configures the logger for this SysLog instance. The logger is set to the DEBUG level,
|
||||||
|
and is equipped with a handler that writes to a file and filters out JSON-like messages.
|
||||||
|
"""
|
||||||
log_path = self._get_log_path()
|
log_path = self._get_log_path()
|
||||||
|
|
||||||
file_handler = logging.FileHandler(filename=log_path)
|
file_handler = logging.FileHandler(filename=log_path)
|
||||||
@@ -41,47 +49,51 @@ class SysLog:
|
|||||||
self.logger.addFilter(_NotJSONFilter())
|
self.logger.addFilter(_NotJSONFilter())
|
||||||
|
|
||||||
def _get_log_path(self) -> Path:
|
def _get_log_path(self) -> Path:
|
||||||
"""Get the path for the log file."""
|
"""
|
||||||
|
Constructs the path for the log file based on the hostname.
|
||||||
|
|
||||||
|
:return: Path object representing the location of the log file.
|
||||||
|
"""
|
||||||
root = Path(__file__).parent.parent.parent.parent.parent.parent / "simulation_output" / self.hostname
|
root = Path(__file__).parent.parent.parent.parent.parent.parent / "simulation_output" / self.hostname
|
||||||
root.mkdir(exist_ok=True, parents=True)
|
root.mkdir(exist_ok=True, parents=True)
|
||||||
return root / f"{self.hostname}_sys.log"
|
return root / f"{self.hostname}_sys.log"
|
||||||
|
|
||||||
def debug(self, msg: str):
|
def debug(self, msg: str):
|
||||||
"""
|
"""
|
||||||
Log a debug message.
|
Logs a message with the DEBUG level.
|
||||||
|
|
||||||
:param msg: The message to log.
|
:param msg: The message to be logged.
|
||||||
"""
|
"""
|
||||||
self.logger.debug(msg)
|
self.logger.debug(msg)
|
||||||
|
|
||||||
def info(self, msg: str):
|
def info(self, msg: str):
|
||||||
"""
|
"""
|
||||||
Log an info message.
|
Logs a message with the INFO level.
|
||||||
|
|
||||||
:param msg: The message to log.
|
:param msg: The message to be logged.
|
||||||
"""
|
"""
|
||||||
self.logger.info(msg)
|
self.logger.info(msg)
|
||||||
|
|
||||||
def warning(self, msg: str):
|
def warning(self, msg: str):
|
||||||
"""
|
"""
|
||||||
Log a warning message.
|
Logs a message with the WARNING level.
|
||||||
|
|
||||||
:param msg: The message to log.
|
:param msg: The message to be logged.
|
||||||
"""
|
"""
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
|
|
||||||
def error(self, msg: str):
|
def error(self, msg: str):
|
||||||
"""
|
"""
|
||||||
Log an error message.
|
Logs a message with the ERROR level.
|
||||||
|
|
||||||
:param msg: The message to log.
|
:param msg: The message to be logged.
|
||||||
"""
|
"""
|
||||||
self.logger.error(msg)
|
self.logger.error(msg)
|
||||||
|
|
||||||
def critical(self, msg: str):
|
def critical(self, msg: str):
|
||||||
"""
|
"""
|
||||||
Log a critical message.
|
Logs a message with the CRITICAL level.
|
||||||
|
|
||||||
:param msg: The message to log.
|
:param msg: The message to be logged.
|
||||||
"""
|
"""
|
||||||
self.logger.critical(msg)
|
self.logger.critical(msg)
|
||||||
@@ -41,4 +41,4 @@ def test_multi_nic():
|
|||||||
|
|
||||||
node_a.ping("192.168.0.11")
|
node_a.ping("192.168.0.11")
|
||||||
|
|
||||||
node_c.ping("10.0.0.12")
|
node_c.ping("10.0.0.12")
|
||||||
Reference in New Issue
Block a user