Merge origin/dev into feature/1859
This commit is contained in:
@@ -1 +1 @@
|
||||
3.0.0a1
|
||||
3.0.0b2dev
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# © Crown-owned copyright 2023, Defence Science and Technology Laboratory UK
|
||||
class PrimaiteError(Exception):
|
||||
"""The root PrimAITe Error."""
|
||||
"""The root PrimAITE Error."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -263,7 +263,7 @@ class FolderObservation(AbstractObservation):
|
||||
self.files.append(FileObservation())
|
||||
while len(self.files) > num_files_per_folder:
|
||||
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)
|
||||
|
||||
self.default_observation = {
|
||||
|
||||
@@ -66,6 +66,17 @@ class Network(SimComponent):
|
||||
)
|
||||
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
|
||||
def routers(self) -> List[Router]:
|
||||
"""The Routers in the Network."""
|
||||
|
||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
||||
|
||||
import re
|
||||
import secrets
|
||||
from enum import Enum
|
||||
from ipaddress import IPv4Address, IPv4Network
|
||||
from pathlib import Path
|
||||
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.domain.account import Account
|
||||
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.transmission.data_link_layer import EthernetHeader, Frame
|
||||
from primaite.simulator.network.transmission.network_layer import ICMPPacket, ICMPType, IPPacket, IPProtocol
|
||||
@@ -886,19 +886,6 @@ class ICMP:
|
||||
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):
|
||||
"""
|
||||
A basic Node class that represents a node on the network.
|
||||
@@ -1176,18 +1163,21 @@ class Node(SimComponent):
|
||||
else:
|
||||
if self.operating_state == NodeOperatingState.BOOTING:
|
||||
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():
|
||||
if nic._connected_link:
|
||||
nic.enable()
|
||||
|
||||
self._start_up_actions()
|
||||
|
||||
# count down to shut down
|
||||
if self.shut_down_countdown > 0:
|
||||
self.shut_down_countdown -= 1
|
||||
else:
|
||||
if self.operating_state == NodeOperatingState.SHUTTING_DOWN:
|
||||
self.operating_state = NodeOperatingState.OFF
|
||||
self.sys_log.info("Turned off")
|
||||
self.sys_log.info(f"{self.hostname}: Turned off")
|
||||
self._shut_down_actions()
|
||||
|
||||
# if resetting turn back on
|
||||
if self.is_resetting:
|
||||
@@ -1283,6 +1273,7 @@ class Node(SimComponent):
|
||||
self.start_up_countdown = self.start_up_duration
|
||||
|
||||
if self.start_up_duration <= 0:
|
||||
self._start_up_actions()
|
||||
self.operating_state = NodeOperatingState.ON
|
||||
self.sys_log.info("Turned on")
|
||||
for nic in self.nics.values():
|
||||
@@ -1298,6 +1289,7 @@ class Node(SimComponent):
|
||||
self.shut_down_countdown = self.shut_down_duration
|
||||
|
||||
if self.shut_down_duration <= 0:
|
||||
self._shut_down_actions()
|
||||
self.operating_state = NodeOperatingState.OFF
|
||||
self.sys_log.info("Turned off")
|
||||
|
||||
@@ -1504,6 +1496,34 @@ class Node(SimComponent):
|
||||
_LOGGER.info(f"Removed application {application.uuid} from node {self.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:
|
||||
if isinstance(item, Service):
|
||||
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):
|
||||
"""Status code of the current FTP request."""
|
||||
|
||||
NOT_FOUND = 14
|
||||
"""Destination not found."""
|
||||
|
||||
OK = 200
|
||||
"""Command successful."""
|
||||
|
||||
|
||||
@@ -2,8 +2,11 @@ from abc import abstractmethod
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, Set
|
||||
|
||||
from primaite import getLogger
|
||||
from primaite.simulator.system.software import IOSoftware, SoftwareHealthState
|
||||
|
||||
_LOGGER = getLogger(__name__)
|
||||
|
||||
|
||||
class ApplicationOperatingState(Enum):
|
||||
"""Enumeration of Application Operating States."""
|
||||
@@ -57,7 +60,7 @@ class Application(IOSoftware):
|
||||
state = super().describe_state()
|
||||
state.update(
|
||||
{
|
||||
"opearting_state": self.operating_state.value,
|
||||
"operating_state": self.operating_state.value,
|
||||
"execution_control_status": self.execution_control_status,
|
||||
"num_executions": self.num_executions,
|
||||
"groups": list(self.groups),
|
||||
@@ -65,8 +68,30 @@ class Application(IOSoftware):
|
||||
)
|
||||
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:
|
||||
"""Open the Application."""
|
||||
if not super()._can_perform_action():
|
||||
return
|
||||
|
||||
if self.operating_state == ApplicationOperatingState.CLOSED:
|
||||
self.sys_log.info(f"Running Application {self.name}")
|
||||
self.operating_state = ApplicationOperatingState.RUNNING
|
||||
@@ -83,6 +108,9 @@ class Application(IOSoftware):
|
||||
|
||||
def install(self) -> None:
|
||||
"""Install Application."""
|
||||
if self._can_perform_action():
|
||||
return
|
||||
|
||||
super().install()
|
||||
if self.operating_state == ApplicationOperatingState.CLOSED:
|
||||
self.sys_log.info(f"Installing Application {self.name}")
|
||||
@@ -98,4 +126,4 @@ class Application(IOSoftware):
|
||||
:param payload: The payload to receive.
|
||||
:return: True if successful, False otherwise.
|
||||
"""
|
||||
pass
|
||||
return super().receive(payload=payload, session_id=session_id, **kwargs)
|
||||
|
||||
@@ -61,7 +61,10 @@ class DatabaseClient(Application):
|
||||
|
||||
def connect(self) -> bool:
|
||||
"""Connect to a Database Service."""
|
||||
if not self.connected and self.operating_state == ApplicationOperatingState.RUNNING:
|
||||
if not self._can_perform_action():
|
||||
return False
|
||||
|
||||
if not self.connected:
|
||||
return self._connect(self.server_ip_address, self.server_password)
|
||||
return False
|
||||
|
||||
@@ -142,20 +145,31 @@ class DatabaseClient(Application):
|
||||
if self.operating_state == ApplicationOperatingState.RUNNING:
|
||||
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.
|
||||
|
||||
: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.
|
||||
"""
|
||||
if self.connected and self.operating_state == ApplicationOperatingState.RUNNING:
|
||||
if not self._can_perform_action():
|
||||
return False
|
||||
|
||||
if self.connected:
|
||||
query_id = str(uuid4())
|
||||
|
||||
# Initialise the tracker of this ID to False
|
||||
self._query_success_tracker[query_id] = False
|
||||
return self._query(sql=sql, query_id=query_id)
|
||||
return False
|
||||
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:
|
||||
"""
|
||||
|
||||
@@ -3,7 +3,12 @@ from typing import Dict, Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from primaite.simulator.core import RequestManager, RequestType
|
||||
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.transport_layer import Port
|
||||
from primaite.simulator.system.applications.application import Application
|
||||
@@ -72,8 +77,11 @@ class WebBrowser(Application):
|
||||
:type: url: str
|
||||
"""
|
||||
url = self.target_url
|
||||
if not self._can_perform_action():
|
||||
return False
|
||||
|
||||
# reset latest response
|
||||
self.latest_response = None
|
||||
self.latest_response = HttpResponsePacket(status_code=HttpStatusCode.NOT_FOUND)
|
||||
|
||||
try:
|
||||
parsed_url = urlparse(url)
|
||||
@@ -102,11 +110,19 @@ class WebBrowser(Application):
|
||||
payload = HttpRequestPacket(request_method=HttpRequestMethod.GET, request_url=url)
|
||||
|
||||
# send request
|
||||
return self.send(
|
||||
if self.send(
|
||||
payload=payload,
|
||||
dest_ip_address=self.domain_name_ip_address,
|
||||
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(
|
||||
self,
|
||||
|
||||
@@ -65,6 +65,10 @@ class DatabaseService(Service):
|
||||
|
||||
def backup_database(self) -> bool:
|
||||
"""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
|
||||
if self.backup_server is None:
|
||||
self.sys_log.error(f"{self.name} - {self.sys_log.hostname}: not configured.")
|
||||
@@ -90,6 +94,10 @@ class DatabaseService(Service):
|
||||
|
||||
def restore_backup(self) -> bool:
|
||||
"""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
|
||||
ftp_client_service: FTPClient = software_manager.software["FTPClient"]
|
||||
|
||||
@@ -190,6 +198,9 @@ class DatabaseService(Service):
|
||||
:param session_id: The session identifier.
|
||||
: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": []}
|
||||
if isinstance(payload, dict) and payload.get("type"):
|
||||
if payload["type"] == "connect_request":
|
||||
|
||||
@@ -53,14 +53,27 @@ class DNSClient(Service):
|
||||
state = super().describe_state()
|
||||
return state
|
||||
|
||||
def add_domain_to_cache(self, domain_name: str, ip_address: IPv4Address):
|
||||
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 add_domain_to_cache(self, domain_name: str, ip_address: IPv4Address) -> bool:
|
||||
"""
|
||||
Adds a domain name to the DNS Client cache.
|
||||
|
||||
:param: domain_name: The domain name to save to cache
|
||||
: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
|
||||
return True
|
||||
|
||||
def check_domain_exists(
|
||||
self,
|
||||
@@ -74,6 +87,9 @@ class DNSClient(Service):
|
||||
: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.
|
||||
"""
|
||||
if not self._can_perform_action():
|
||||
return False
|
||||
|
||||
# check if DNS server is configured
|
||||
if self.dns_server is None:
|
||||
self.sys_log.error(f"{self.name}: DNS Server is not configured")
|
||||
|
||||
@@ -64,6 +64,9 @@ class DNSServer(Service):
|
||||
:param target_domain: The single domain name requested by a DNS client.
|
||||
: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)
|
||||
|
||||
def dns_register(self, domain_name: str, domain_ip_address: IPv4Address):
|
||||
@@ -76,6 +79,9 @@ class DNSServer(Service):
|
||||
:param: domain_ip_address: The IP address that the domain should route to
|
||||
:type: domain_ip_address: IPv4Address
|
||||
"""
|
||||
if not self._can_perform_action():
|
||||
return
|
||||
|
||||
self.dns_table[domain_name] = domain_ip_address
|
||||
|
||||
def receive(
|
||||
@@ -95,10 +101,14 @@ class DNSServer(Service):
|
||||
|
||||
: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
|
||||
if not isinstance(payload, DNSPacket):
|
||||
_LOGGER.debug(f"{payload} is not a DNSPacket")
|
||||
return False
|
||||
|
||||
# cast payload into a DNS packet
|
||||
payload: DNSPacket = payload
|
||||
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
|
||||
# create FTP packet
|
||||
payload: FTPPacket = FTPPacket(
|
||||
ftp_command=FTPCommand.PORT,
|
||||
ftp_command_args=Port.FTP,
|
||||
)
|
||||
payload: FTPPacket = FTPPacket(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 payload.status_code == FTPStatusCode.OK:
|
||||
@@ -271,7 +268,10 @@ class FTPClient(FTPServiceABC):
|
||||
the same node.
|
||||
"""
|
||||
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
|
||||
|
||||
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)
|
||||
return True
|
||||
|
||||
@@ -86,6 +86,9 @@ class FTPServer(FTPServiceABC):
|
||||
prevents an FTP request loop - FTP client and servers can exist on
|
||||
the same node.
|
||||
"""
|
||||
if not super().receive(payload=payload, session_id=session_id, **kwargs):
|
||||
return False
|
||||
|
||||
if payload.status_code is not None:
|
||||
return False
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from enum import Enum
|
||||
from typing import Dict, Optional
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from primaite import getLogger
|
||||
from primaite.simulator.core import RequestManager, RequestType
|
||||
@@ -40,6 +40,40 @@ class Service(IOSoftware):
|
||||
restart_countdown: Optional[int] = None
|
||||
"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):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@@ -88,6 +122,10 @@ class Service(IOSoftware):
|
||||
|
||||
def start(self, **kwargs) -> None:
|
||||
"""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:
|
||||
self.sys_log.info(f"Starting service {self.name}")
|
||||
self.operating_state = ServiceOperatingState.RUNNING
|
||||
|
||||
@@ -170,6 +170,9 @@ class WebServer(Service):
|
||||
:param: payload: The payload to send.
|
||||
: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
|
||||
if not isinstance(payload, HttpRequestPacket):
|
||||
self.sys_log.error("Payload is not an HTTPPacket")
|
||||
|
||||
@@ -3,8 +3,9 @@ from enum import Enum
|
||||
from ipaddress import IPv4Address
|
||||
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.network.hardware.node_operating_state import NodeOperatingState
|
||||
from primaite.simulator.network.transmission.transport_layer import Port
|
||||
from primaite.simulator.system.core.session_manager import Session
|
||||
from primaite.simulator.system.core.sys_log import SysLog
|
||||
@@ -234,6 +235,21 @@ class IOSoftware(Software):
|
||||
)
|
||||
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(
|
||||
self,
|
||||
payload: Any,
|
||||
@@ -252,6 +268,9 @@ class IOSoftware(Software):
|
||||
|
||||
:return: True if successful, False otherwise.
|
||||
"""
|
||||
if not self._can_perform_action():
|
||||
return False
|
||||
|
||||
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
|
||||
)
|
||||
@@ -270,4 +289,5 @@ class IOSoftware(Software):
|
||||
:param kwargs: Additional keyword arguments specific to the implementation.
|
||||
: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()
|
||||
|
||||
Reference in New Issue
Block a user