From 2ee820339779de50eb69e97c1e11f8261eaa3359 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Mon, 13 Nov 2023 17:39:27 +0000 Subject: [PATCH 01/33] #2041: NTP server initial commits --- .../simulator/network/protocols/ntp.py | 34 +++++++++++ .../simulator/system/services/ntp/__init__.py | 0 .../system/services/ntp/ntp_server.py | 56 +++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 src/primaite/simulator/network/protocols/ntp.py create mode 100644 src/primaite/simulator/system/services/ntp/__init__.py create mode 100644 src/primaite/simulator/system/services/ntp/ntp_server.py diff --git a/src/primaite/simulator/network/protocols/ntp.py b/src/primaite/simulator/network/protocols/ntp.py new file mode 100644 index 00000000..f14dab73 --- /dev/null +++ b/src/primaite/simulator/network/protocols/ntp.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from ipaddress import IPv4Address +from typing import Optional + +from pydantic import BaseModel + +from primaite.simulator.network.protocols.packet import DataPacket + + +class NTPRequest(BaseModel): + """Represents a NTP Request packet.""" + + pass + + +class NTPReply(BaseModel): + """Represents a NTP Reply packet.""" + + pass + + +class NTPPacket(DataPacket): + """ + Represents the NTP layer of a network frame. + + :param ntp_request: NTPRequest packet from NTP client. + :param ntp_reply: NTPReply packet from NTP Server. + """ + + ntp_request: NTPRequest + "NTP Request packet sent by NTP Client." + ntp_reply: Optional[NTPReply] = None + "NTP Reply packet generated by NTP Server." diff --git a/src/primaite/simulator/system/services/ntp/__init__.py b/src/primaite/simulator/system/services/ntp/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/primaite/simulator/system/services/ntp/ntp_server.py b/src/primaite/simulator/system/services/ntp/ntp_server.py new file mode 100644 index 00000000..914dd1c3 --- /dev/null +++ b/src/primaite/simulator/system/services/ntp/ntp_server.py @@ -0,0 +1,56 @@ +from ipaddress import IPv4Address +from typing import Any, Dict, Optional + +from primaite import getLogger +from primaite.simulator.network.transmission.network_layer import IPProtocol +from primaite.simulator.network.transmission.transport_layer import Port +from primaite.simulator.system.services.service import Service + +_LOGGER = getLogger(__name__) + +class NTPServer(Service): + """Represents a NTP server as a service""" + + def __init__(self, **kwargs): + kwargs["name"] = "NTPServer" + kwargs["port"] = Port.NTP + kwargs["protocol"] = IPProtocol.UDP + super().__init__(**kwargs) + self.start() + + 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 + """ + state = super().describe_state() + return state + + 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 receive( + self, + payload: Any, + session_id: Optional[str] = None, + **kwargs, + ) -> bool: + """Receives a request from NTPClient""" + pass + + def send(self): + """Sends time data to NTPClient""" + pass From 764d9561bd3ef45520b9bd6ec50614d244e00e78 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Mon, 13 Nov 2023 17:40:25 +0000 Subject: [PATCH 02/33] #2042: NTP client initial commit --- .../system/services/ntp/ntp_client.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/primaite/simulator/system/services/ntp/ntp_client.py diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py new file mode 100644 index 00000000..edae9af6 --- /dev/null +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -0,0 +1,50 @@ +from ipaddress import IPv4Address +from typing import Dict, Optional +from primaite.simulator.network.transmission.network_layer import IPProtocol +from primaite.simulator.network.transmission.transport_layer import Port +from primaite.simulator.system.services.service import Service + + +from primaite import getLogger + +_LOGGER = getLogger(__name__) + + +class NTPClient(Service): + """Represents a NTP client as a service""" + + ntp_server: Optional[IPv4Address] = None + "The NTP server the client sends requests to." + + def __init__(self, **kwargs): + kwargs["name"] = "NTPClient" + kwargs["port"] = Port.NTP + kwargs["protocol"] = IPProtocol.UDP + super().__init__(**kwargs) + self.start() + + 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 + """ + state = super().describe_state() + return state + + 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 receive(self): + """Receives time data from server""" + pass From f32048712880d3951404656c10bdb1c86fd55a47 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 14 Nov 2023 15:13:05 +0000 Subject: [PATCH 03/33] #2041: Implement NTP protocol for server --- .../simulator/network/protocols/ntp.py | 18 +++++++--- .../system/services/ntp/ntp_server.py | 36 ++++++++++++++++--- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/primaite/simulator/network/protocols/ntp.py b/src/primaite/simulator/network/protocols/ntp.py index f14dab73..89a26961 100644 --- a/src/primaite/simulator/network/protocols/ntp.py +++ b/src/primaite/simulator/network/protocols/ntp.py @@ -2,22 +2,22 @@ from __future__ import annotations from ipaddress import IPv4Address from typing import Optional - from pydantic import BaseModel - from primaite.simulator.network.protocols.packet import DataPacket +from datetime import datetime class NTPRequest(BaseModel): """Represents a NTP Request packet.""" - pass + ntp_client: IPv4Address = None class NTPReply(BaseModel): """Represents a NTP Reply packet.""" - pass + ntp_datetime: datetime + "NTP datetime object set by NTP Server." class NTPPacket(DataPacket): @@ -31,4 +31,12 @@ class NTPPacket(DataPacket): ntp_request: NTPRequest "NTP Request packet sent by NTP Client." ntp_reply: Optional[NTPReply] = None - "NTP Reply packet generated by NTP Server." + + def generate_reply(self, time: datetime) -> NTPPacket: + """ Generate a NTPPacket containing the time in a NTPReply object + + :param time: datetime object representing the time from the NTP server. + :return: A new NTPPacket object. + """ + self.ntp_reply = NTPReply(time) + return self diff --git a/src/primaite/simulator/system/services/ntp/ntp_server.py b/src/primaite/simulator/system/services/ntp/ntp_server.py index 914dd1c3..50a582a4 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_server.py +++ b/src/primaite/simulator/system/services/ntp/ntp_server.py @@ -2,12 +2,15 @@ from ipaddress import IPv4Address from typing import Any, Dict, Optional from primaite import getLogger +from primaite.simulator.network.protocols.ntp import NTPPacket from primaite.simulator.network.transmission.network_layer import IPProtocol from primaite.simulator.network.transmission.transport_layer import Port from primaite.simulator.system.services.service import Service +from datetime import datetime _LOGGER = getLogger(__name__) + class NTPServer(Service): """Represents a NTP server as a service""" @@ -48,9 +51,32 @@ class NTPServer(Service): session_id: Optional[str] = None, **kwargs, ) -> bool: - """Receives a request from NTPClient""" - pass + """Receives a request from NTPClient. - def send(self): - """Sends time data to NTPClient""" - pass + Check that request has a valid IP address. + + :param payload: The payload to send. + :param session_id: Id of the session (Optional). + + :return: True if valid NTP request else False. + """ + if not (isinstance(payload, NTPPacket) and + payload.ntp_request.ntp_client): + _LOGGER.debug(f"{payload} is not a NTPPacket") + return False + payload: NTPPacket = payload + if payload.ntp_request.ntp_client: + self.sys_log.info( + f"{self.name}: Received request for {payload.ntp_request.ntp_client} " + f"from session {session_id}" + ) + # generate a reply with the current time + time = datetime.now() + payload = payload.generate_reply(time) + self.sys_log.info( + f"{self.name}: Responding to NTP request for {payload.ntp_request.ntp_client} " + f"with current time: {time}" + ) + # send reply + self.send(payload, session_id) + return True From 195e8a4e84507eb30125b57160fb7b7038648260 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 14 Nov 2023 15:13:28 +0000 Subject: [PATCH 04/33] #2042: Implement NTP protocol for client --- .../system/services/ntp/ntp_client.py | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index edae9af6..d1fe7bbf 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -1,5 +1,6 @@ from ipaddress import IPv4Address from typing import Dict, Optional +from primaite.simulator.network.protocols.ntp import NTPPacket from primaite.simulator.network.transmission.network_layer import IPProtocol from primaite.simulator.network.transmission.transport_layer import Port from primaite.simulator.system.services.service import Service @@ -45,6 +46,48 @@ class NTPClient(Service): """ pass - def receive(self): - """Receives time data from server""" - pass + def send( + self, + payload: NTPPacket, + session_id: Optional[str] = None, + dest_ip_address: Optional[IPv4Address] = None, + dest_port: [Port] = Port.NTP, + **kwargs, + ) -> bool: + """Requests NTP data from NTP server. + + :param payload: The payload to be sent. + :param session_id: The Session ID the payload is to originate from. Optional. + :param dest_ip_address: The ip address of the payload destination. + :param dest_port: The port of the payload destination. + + :return: True if successful, False otherwise. + """ + self.sys_log.info(f"{self.name}: Sending NTP request {payload.ntp_request.ntp_client}") + + return super().send( + payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id, **kwargs + ) + + def receive( + self, + payload: NTPPacket, + session_id: Optional[str] = None, + **kwargs, + ): + """Receives time data from server + + :param payload: The payload to be sent. + :param session_id: The Session ID the payload is to originate from. Optional. + :return: True if successful, False otherwise. + """ + if not (isinstance(payload, NTPPacket) and + payload.ntp_request.ntp_client): + _LOGGER.debug(f"{payload} is not a NTPPacket") + return False + + # XXX: compare received datetime with current time. Log error if differ by more than x ms? + if payload.ntp_reply.ntp_datetime: + self.sys_log.info( + f"{self.name}: Received time update from NTP server{payload.ntp_reply.ntp_datetime}") + return True From d31fce202cbada0504b37fa30581578e80da6125 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 15 Nov 2023 10:57:56 +0000 Subject: [PATCH 05/33] #2041: pre-commit changes. --- .../simulator/network/protocols/ntp.py | 6 ++++-- .../system/services/ntp/ntp_server.py | 19 ++++++++----------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/primaite/simulator/network/protocols/ntp.py b/src/primaite/simulator/network/protocols/ntp.py index 89a26961..286c5664 100644 --- a/src/primaite/simulator/network/protocols/ntp.py +++ b/src/primaite/simulator/network/protocols/ntp.py @@ -1,10 +1,12 @@ from __future__ import annotations +from datetime import datetime from ipaddress import IPv4Address from typing import Optional + from pydantic import BaseModel + from primaite.simulator.network.protocols.packet import DataPacket -from datetime import datetime class NTPRequest(BaseModel): @@ -33,7 +35,7 @@ class NTPPacket(DataPacket): ntp_reply: Optional[NTPReply] = None def generate_reply(self, time: datetime) -> NTPPacket: - """ Generate a NTPPacket containing the time in a NTPReply object + """Generate a NTPPacket containing the time in a NTPReply object. :param time: datetime object representing the time from the NTP server. :return: A new NTPPacket object. diff --git a/src/primaite/simulator/system/services/ntp/ntp_server.py b/src/primaite/simulator/system/services/ntp/ntp_server.py index 50a582a4..d4be6924 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_server.py +++ b/src/primaite/simulator/system/services/ntp/ntp_server.py @@ -1,4 +1,4 @@ -from ipaddress import IPv4Address +from datetime import datetime from typing import Any, Dict, Optional from primaite import getLogger @@ -6,13 +6,12 @@ from primaite.simulator.network.protocols.ntp import NTPPacket from primaite.simulator.network.transmission.network_layer import IPProtocol from primaite.simulator.network.transmission.transport_layer import Port from primaite.simulator.system.services.service import Service -from datetime import datetime _LOGGER = getLogger(__name__) class NTPServer(Service): - """Represents a NTP server as a service""" + """Represents a NTP server as a service.""" def __init__(self, **kwargs): kwargs["name"] = "NTPServer" @@ -46,10 +45,10 @@ class NTPServer(Service): pass def receive( - self, - payload: Any, - session_id: Optional[str] = None, - **kwargs, + self, + payload: Any, + session_id: Optional[str] = None, + **kwargs, ) -> bool: """Receives a request from NTPClient. @@ -60,15 +59,13 @@ class NTPServer(Service): :return: True if valid NTP request else False. """ - if not (isinstance(payload, NTPPacket) and - payload.ntp_request.ntp_client): + if not (isinstance(payload, NTPPacket) and payload.ntp_request.ntp_client): _LOGGER.debug(f"{payload} is not a NTPPacket") return False payload: NTPPacket = payload if payload.ntp_request.ntp_client: self.sys_log.info( - f"{self.name}: Received request for {payload.ntp_request.ntp_client} " - f"from session {session_id}" + f"{self.name}: Received request for {payload.ntp_request.ntp_client} " f"from session {session_id}" ) # generate a reply with the current time time = datetime.now() From 9deb130d10bc699931bcdae141075d5d68cf2d00 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 15 Nov 2023 10:58:24 +0000 Subject: [PATCH 06/33] #2042: pre-commit changes --- .../system/services/ntp/ntp_client.py | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index d1fe7bbf..0e3646ae 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -1,18 +1,17 @@ from ipaddress import IPv4Address from typing import Dict, Optional + +from primaite import getLogger from primaite.simulator.network.protocols.ntp import NTPPacket from primaite.simulator.network.transmission.network_layer import IPProtocol from primaite.simulator.network.transmission.transport_layer import Port from primaite.simulator.system.services.service import Service - -from primaite import getLogger - _LOGGER = getLogger(__name__) class NTPClient(Service): - """Represents a NTP client as a service""" + """Represents a NTP client as a service.""" ntp_server: Optional[IPv4Address] = None "The NTP server the client sends requests to." @@ -47,12 +46,12 @@ class NTPClient(Service): pass def send( - self, - payload: NTPPacket, - session_id: Optional[str] = None, - dest_ip_address: Optional[IPv4Address] = None, - dest_port: [Port] = Port.NTP, - **kwargs, + self, + payload: NTPPacket, + session_id: Optional[str] = None, + dest_ip_address: IPv4Address = ntp_server, + dest_port: [Port] = Port.NTP, + **kwargs, ) -> bool: """Requests NTP data from NTP server. @@ -74,20 +73,18 @@ class NTPClient(Service): payload: NTPPacket, session_id: Optional[str] = None, **kwargs, - ): - """Receives time data from server + ) -> bool: + """Receives time data from server. :param payload: The payload to be sent. :param session_id: The Session ID the payload is to originate from. Optional. :return: True if successful, False otherwise. """ - if not (isinstance(payload, NTPPacket) and - payload.ntp_request.ntp_client): + if not (isinstance(payload, NTPPacket) and payload.ntp_request.ntp_client): _LOGGER.debug(f"{payload} is not a NTPPacket") return False # XXX: compare received datetime with current time. Log error if differ by more than x ms? if payload.ntp_reply.ntp_datetime: - self.sys_log.info( - f"{self.name}: Received time update from NTP server{payload.ntp_reply.ntp_datetime}") + self.sys_log.info(f"{self.name}: Received time update from NTP server{payload.ntp_reply.ntp_datetime}") return True From 0c544a9a263d7fb05d1ab16bc1647946043ea2f3 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 15 Nov 2023 15:58:10 +0000 Subject: [PATCH 07/33] #2042: Add support for apply_timestep() --- .../simulator/network/protocols/ntp.py | 2 +- .../system/services/ntp/ntp_client.py | 36 ++++++++++++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/primaite/simulator/network/protocols/ntp.py b/src/primaite/simulator/network/protocols/ntp.py index 286c5664..e201a770 100644 --- a/src/primaite/simulator/network/protocols/ntp.py +++ b/src/primaite/simulator/network/protocols/ntp.py @@ -12,7 +12,7 @@ from primaite.simulator.network.protocols.packet import DataPacket class NTPRequest(BaseModel): """Represents a NTP Request packet.""" - ntp_client: IPv4Address = None + ntp_client: Optional[IPv4Address] = None class NTPReply(BaseModel): diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index 0e3646ae..e305970a 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -2,10 +2,10 @@ from ipaddress import IPv4Address from typing import Dict, Optional from primaite import getLogger -from primaite.simulator.network.protocols.ntp import NTPPacket +from primaite.simulator.network.protocols.ntp import NTPPacket, NTPRequest from primaite.simulator.network.transmission.network_layer import IPProtocol from primaite.simulator.network.transmission.transport_layer import Port -from primaite.simulator.system.services.service import Service +from primaite.simulator.system.services.service import Service, ServiceOperatingState _LOGGER = getLogger(__name__) @@ -13,6 +13,7 @@ _LOGGER = getLogger(__name__) class NTPClient(Service): """Represents a NTP client as a service.""" + ip_addr: Optional[IPv4Address] = None ntp_server: Optional[IPv4Address] = None "The NTP server the client sends requests to." @@ -30,7 +31,8 @@ class NTPClient(Service): 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. + :return: A dictionary containing key-value pairs representing the current state + of the software. :rtype: Dict """ state = super().describe_state() @@ -65,7 +67,11 @@ class NTPClient(Service): self.sys_log.info(f"{self.name}: Sending NTP request {payload.ntp_request.ntp_client}") return super().send( - payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id, **kwargs + payload=payload, + dest_ip_address=dest_ip_address, + dest_port=dest_port, + session_id=session_id, + **kwargs, ) def receive( @@ -86,5 +92,25 @@ class NTPClient(Service): # XXX: compare received datetime with current time. Log error if differ by more than x ms? if payload.ntp_reply.ntp_datetime: - self.sys_log.info(f"{self.name}: Received time update from NTP server{payload.ntp_reply.ntp_datetime}") + self.sys_log.info( + f"{self.name}: Received time \ + update from NTP server{payload.ntp_reply.ntp_datetime}" + ) return True + + def apply_timestep(self, timestep: int) -> None: + """ + For each timestep request the time tfrom the NTP server. + + In this instance, if any multi-timestep processes are currently + occurring (such as restarting or installation), then they are brought one step closer to + being finished. + + :param timestep: The current timestep number. (Amount of time since simulation episode began) + :type timestep: int + """ + super().apply_timestep(timestep) + if self.operating_state == ServiceOperatingState.RUNNING: + # request time from server + ntp_request = NTPPacket(NTPRequest()) + self.send(ntp_request) From 7ee2c4220a6eb80e0ed00e9a1000ab32df19aa60 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Thu, 16 Nov 2023 15:04:01 +0000 Subject: [PATCH 08/33] #2042: ntp_client test --- .../system/services/ntp/ntp_client.py | 6 ++++- .../system/test_ntp_client_server.py | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/integration_tests/system/test_ntp_client_server.py diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index e305970a..123de7cc 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -100,7 +100,7 @@ class NTPClient(Service): def apply_timestep(self, timestep: int) -> None: """ - For each timestep request the time tfrom the NTP server. + For each timestep request the time from the NTP server. In this instance, if any multi-timestep processes are currently occurring (such as restarting or installation), then they are brought one step closer to @@ -114,3 +114,7 @@ class NTPClient(Service): # request time from server ntp_request = NTPPacket(NTPRequest()) self.send(ntp_request) + return True + else: + self.sys_log.debug(f"{self.name} ntp client not running") + return False diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py new file mode 100644 index 00000000..5d301d2b --- /dev/null +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -0,0 +1,25 @@ +from primaite.simulator.network.hardware.nodes.computer import Computer +from primaite.simulator.network.hardware.nodes.server import Server +from primaite.simulator.network.protocols.ntp import NTPPacket +from primaite.simulator.system.services.ntp.ntp_client import NTPClient +from primaite.simulator.system.services.ntp.ntp_server import NTPServer +from primaite.simulator.system.services.service import ServiceOperatingState + +# Define one node to be an NTP server and another node to be a NTP Client. + + +def test_ntp_client_server(network): + server: Server = network.get_node_by_hostname("ntp_server") + client: Computer = network.get_node_by_hostname("ntp_client") + + ntp_server: NTPServer = server.software_manager.software["NTP_Server"] + ntp_client: NTPClient = client.software_manager.software["NTP_Client"] + + assert ntp_server.operating_state == ServiceOperatingState.RUNNING + assert ntp_client.operating_state == ServiceOperatingState.RUNNING + + ntp_client.send(payload=NTPPacket()) + assert ntp_server.receive() is True + assert ntp_client.receive() is True + + assert ntp_client.apply_timestep(1) is True From 622c6931d8df352f7fd96b0a1ea58ec3323db98e Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Fri, 17 Nov 2023 12:07:46 +0000 Subject: [PATCH 09/33] #2041: Create test network + extra test --- .../system/test_ntp_client_server.py | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index 5d301d2b..61c8740b 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -1,3 +1,8 @@ +from ipaddress import IPv4Address + +import pytest + +from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.nodes.computer import Computer from primaite.simulator.network.hardware.nodes.server import Server from primaite.simulator.network.protocols.ntp import NTPPacket @@ -5,10 +10,41 @@ from primaite.simulator.system.services.ntp.ntp_client import NTPClient from primaite.simulator.system.services.ntp.ntp_server import NTPServer from primaite.simulator.system.services.service import ServiceOperatingState +# Create simple network for testing + + +def create_ntp_network() -> Network: + """ + +------------+ +------------+ + | ntp | | ntp | + | client_1 +------------+ server_1 | + | | | | + +------------+ +------------+ + + """ + + network = Network() + ntp_server = Server( + hostname="ntp_server", ip_address="192.168.1.2", subnet_mask="255.255.255.0", default_gateway="192.168.1.1" + ) + ntp_server.power_on() + + ntp_client = Computer( + hostname="ntp_client", ip_address="192.168.1.3", subnet_mask="255.255.255.0", default_gateway="192.168.1.1" + ) + ntp_client.power_on() + network.connect(endpoint_b=ntp_server.ethernet_port[1], endpoint_a=ntp_client.ethernet_port[1]) + + +# @pytest.fixture() +# def create_network(): +# return create_ntp_network() + # Define one node to be an NTP server and another node to be a NTP Client. -def test_ntp_client_server(network): +def test_ntp_client_server(): + network = create_ntp_network() server: Server = network.get_node_by_hostname("ntp_server") client: Computer = network.get_node_by_hostname("ntp_client") @@ -23,3 +59,24 @@ def test_ntp_client_server(network): assert ntp_client.receive() is True assert ntp_client.apply_timestep(1) is True + + +# Test ntp client behaviour when ntp server is unavailable. +def test_ntp_server_failure(): + network = create_ntp_network() + server: Server = network.get_node_by_hostname("ntp_server") + client: Computer = network.get_node_by_hostname("ntp_client") + + ntp_server: NTPServer = server.software_manager.software["NTP_Server"] + ntp_client: NTPClient = client.software_manager.software["NTP_Client"] + + assert ntp_client.operating_state == ServiceOperatingState.RUNNING + + # Turn off ntp server. + ntp_server.stop() + assert ntp_server.operating_state == ServiceOperatingState.STOPPED + assert ntp_client.receive() is False + + # Restart ntp server. + ntp_server.start() + assert ntp_server.operating_state == ServiceOperatingState.RUNNING From 6081f02caa68982a0c19dbee235ff91bfcea5ff6 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Fri, 17 Nov 2023 12:41:53 +0000 Subject: [PATCH 10/33] #2041: Add missing return statement --- tests/integration_tests/system/test_ntp_client_server.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index 61c8740b..0e3567ae 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -35,6 +35,8 @@ def create_ntp_network() -> Network: ntp_client.power_on() network.connect(endpoint_b=ntp_server.ethernet_port[1], endpoint_a=ntp_client.ethernet_port[1]) + return network + # @pytest.fixture() # def create_network(): From dbecc681dc47f916f5c181fa72d1b95e5852b2cc Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Fri, 17 Nov 2023 12:59:06 +0000 Subject: [PATCH 11/33] #2041: Install NTPServer and NTPClient. --- tests/integration_tests/system/test_ntp_client_server.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index 0e3567ae..dc487164 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -28,11 +28,13 @@ def create_ntp_network() -> Network: hostname="ntp_server", ip_address="192.168.1.2", subnet_mask="255.255.255.0", default_gateway="192.168.1.1" ) ntp_server.power_on() + ntp_server.software_manager.install(NTPServer) ntp_client = Computer( hostname="ntp_client", ip_address="192.168.1.3", subnet_mask="255.255.255.0", default_gateway="192.168.1.1" ) ntp_client.power_on() + ntp_client.software_manager.install(NTPClient) network.connect(endpoint_b=ntp_server.ethernet_port[1], endpoint_a=ntp_client.ethernet_port[1]) return network From d28bd0d1c36217489c75d0663d3c3c0091776bcd Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Fri, 17 Nov 2023 14:19:36 +0000 Subject: [PATCH 12/33] #2041: Fix names --- tests/integration_tests/system/test_ntp_client_server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index dc487164..e859faf4 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -52,8 +52,8 @@ def test_ntp_client_server(): server: Server = network.get_node_by_hostname("ntp_server") client: Computer = network.get_node_by_hostname("ntp_client") - ntp_server: NTPServer = server.software_manager.software["NTP_Server"] - ntp_client: NTPClient = client.software_manager.software["NTP_Client"] + ntp_server: NTPServer = server.software_manager.software["NTPServer"] + ntp_client: NTPClient = client.software_manager.software["NTPClient"] assert ntp_server.operating_state == ServiceOperatingState.RUNNING assert ntp_client.operating_state == ServiceOperatingState.RUNNING From 95ad55a78369f92776e46a95cf5fce421d27f194 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Mon, 20 Nov 2023 18:04:49 +0000 Subject: [PATCH 13/33] #2041: change deprecated logger levels. --- src/primaite/simulator/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/primaite/simulator/core.py b/src/primaite/simulator/core.py index 9ead877e..5ec816bb 100644 --- a/src/primaite/simulator/core.py +++ b/src/primaite/simulator/core.py @@ -113,7 +113,7 @@ class RequestManager(BaseModel): """ if name in self.request_types: msg = f"Overwriting request type {name}." - _LOGGER.warn(msg) + _LOGGER.warning(msg) self.request_types[name] = request_type @@ -248,6 +248,6 @@ class SimComponent(BaseModel): def parent(self, new_parent: Union["SimComponent", None]) -> None: if self._parent and new_parent: msg = f"Overwriting parent of {self.uuid}. Old parent: {self._parent.uuid}, New parent: {new_parent.uuid}" - _LOGGER.warn(msg) + _LOGGER.warning(msg) raise RuntimeWarning(msg) self._parent = new_parent From b0b37f9da5ce255acfa246c6609adcea725763be Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Mon, 20 Nov 2023 18:06:50 +0000 Subject: [PATCH 14/33] #2042: ntp_client test fixes. --- src/primaite/simulator/system/services/ntp/ntp_client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index 123de7cc..3e73eee7 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -89,8 +89,7 @@ class NTPClient(Service): if not (isinstance(payload, NTPPacket) and payload.ntp_request.ntp_client): _LOGGER.debug(f"{payload} is not a NTPPacket") return False - - # XXX: compare received datetime with current time. Log error if differ by more than x ms? + print(f">>>>>>>>>>>>>>>>>> payload.ntp_reply.ntp_datetime {payload.ntp_reply.ntp_datetime}") if payload.ntp_reply.ntp_datetime: self.sys_log.info( f"{self.name}: Received time \ @@ -112,8 +111,9 @@ class NTPClient(Service): super().apply_timestep(timestep) if self.operating_state == ServiceOperatingState.RUNNING: # request time from server - ntp_request = NTPPacket(NTPRequest()) - self.send(ntp_request) + ntp_request = NTPRequest(ntp_client=self.ip_addr) + ntp_server_packet = NTPPacket(ntp_request=ntp_request) + self.send(payload=ntp_server_packet) return True else: self.sys_log.debug(f"{self.name} ntp client not running") From f7215847d414f947c921602044b8bc0e25b03a06 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Mon, 20 Nov 2023 18:08:55 +0000 Subject: [PATCH 15/33] #2041: ntp_server test fixes. --- .../simulator/network/protocols/ntp.py | 4 ++-- .../system/services/ntp/ntp_server.py | 8 ++++--- .../system/test_ntp_client_server.py | 21 ++++++++++++------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/primaite/simulator/network/protocols/ntp.py b/src/primaite/simulator/network/protocols/ntp.py index e201a770..df5ce0c1 100644 --- a/src/primaite/simulator/network/protocols/ntp.py +++ b/src/primaite/simulator/network/protocols/ntp.py @@ -34,11 +34,11 @@ class NTPPacket(DataPacket): "NTP Request packet sent by NTP Client." ntp_reply: Optional[NTPReply] = None - def generate_reply(self, time: datetime) -> NTPPacket: + def generate_reply(self, ntp_server_time: datetime) -> NTPPacket: """Generate a NTPPacket containing the time in a NTPReply object. :param time: datetime object representing the time from the NTP server. :return: A new NTPPacket object. """ - self.ntp_reply = NTPReply(time) + self.ntp_reply = NTPReply(ntp_datetime=ntp_server_time) return self diff --git a/src/primaite/simulator/system/services/ntp/ntp_server.py b/src/primaite/simulator/system/services/ntp/ntp_server.py index d4be6924..337869a4 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_server.py +++ b/src/primaite/simulator/system/services/ntp/ntp_server.py @@ -50,7 +50,8 @@ class NTPServer(Service): session_id: Optional[str] = None, **kwargs, ) -> bool: - """Receives a request from NTPClient. + """ + Receives a request from NTPClient. Check that request has a valid IP address. @@ -75,5 +76,6 @@ class NTPServer(Service): f"with current time: {time}" ) # send reply - self.send(payload, session_id) - return True + if self.send(payload, session_id): + return True + return False diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index e859faf4..545683f3 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -5,7 +5,7 @@ import pytest from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.nodes.computer import Computer from primaite.simulator.network.hardware.nodes.server import Server -from primaite.simulator.network.protocols.ntp import NTPPacket +from primaite.simulator.network.protocols.ntp import NTPPacket, NTPRequest from primaite.simulator.system.services.ntp.ntp_client import NTPClient from primaite.simulator.system.services.ntp.ntp_server import NTPServer from primaite.simulator.system.services.service import ServiceOperatingState @@ -58,9 +58,11 @@ def test_ntp_client_server(): assert ntp_server.operating_state == ServiceOperatingState.RUNNING assert ntp_client.operating_state == ServiceOperatingState.RUNNING - ntp_client.send(payload=NTPPacket()) - assert ntp_server.receive() is True - assert ntp_client.receive() is True + ntp_request = NTPRequest(ntp_client="192.168.1.3") + ntp_packet = NTPPacket(ntp_request=ntp_request) + ntp_client.send(payload=ntp_packet) + assert ntp_server.receive(payload=ntp_packet) is True + assert ntp_client.receive(payload=ntp_packet) is True assert ntp_client.apply_timestep(1) is True @@ -71,15 +73,20 @@ def test_ntp_server_failure(): server: Server = network.get_node_by_hostname("ntp_server") client: Computer = network.get_node_by_hostname("ntp_client") - ntp_server: NTPServer = server.software_manager.software["NTP_Server"] - ntp_client: NTPClient = client.software_manager.software["NTP_Client"] + ntp_server: NTPServer = server.software_manager.software["NTPServer"] + ntp_client: NTPClient = client.software_manager.software["NTPClient"] assert ntp_client.operating_state == ServiceOperatingState.RUNNING # Turn off ntp server. ntp_server.stop() assert ntp_server.operating_state == ServiceOperatingState.STOPPED - assert ntp_client.receive() is False + # And request a time update. + ntp_request = NTPRequest(ntp_client="192.168.1.3") + ntp_packet = NTPPacket(ntp_request=ntp_request) + ntp_client.send(payload=ntp_packet) + assert ntp_server.receive(payload=ntp_packet) is False + assert ntp_client.receive(payload=ntp_packet) is False # Restart ntp server. ntp_server.start() From 813a1f356e9fac0afcd627145150bdb43484deac Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 21 Nov 2023 11:15:07 +0000 Subject: [PATCH 16/33] #2042: Remove debug statement --- src/primaite/simulator/system/services/ntp/ntp_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index 3e73eee7..99bc7584 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -89,7 +89,6 @@ class NTPClient(Service): if not (isinstance(payload, NTPPacket) and payload.ntp_request.ntp_client): _LOGGER.debug(f"{payload} is not a NTPPacket") return False - print(f">>>>>>>>>>>>>>>>>> payload.ntp_reply.ntp_datetime {payload.ntp_reply.ntp_datetime}") if payload.ntp_reply.ntp_datetime: self.sys_log.info( f"{self.name}: Received time \ From 4f0f758ce9b19387cf79ca989fd023792b1a8b35 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 21 Nov 2023 11:16:34 +0000 Subject: [PATCH 17/33] #2041: Correct return value from receive() --- src/primaite/simulator/system/services/ntp/ntp_server.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/primaite/simulator/system/services/ntp/ntp_server.py b/src/primaite/simulator/system/services/ntp/ntp_server.py index 337869a4..238f4f84 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_server.py +++ b/src/primaite/simulator/system/services/ntp/ntp_server.py @@ -76,6 +76,5 @@ class NTPServer(Service): f"with current time: {time}" ) # send reply - if self.send(payload, session_id): - return True - return False + self.send(payload, session_id) + return True From 60d94bf4b56f45c2e60a57ce9b2dce663d37feda Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 21 Nov 2023 11:17:38 +0000 Subject: [PATCH 18/33] #2041: Remove test --- .../system/test_ntp_client_server.py | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index 545683f3..8059defa 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -40,10 +40,6 @@ def create_ntp_network() -> Network: return network -# @pytest.fixture() -# def create_network(): -# return create_ntp_network() - # Define one node to be an NTP server and another node to be a NTP Client. @@ -67,27 +63,28 @@ def test_ntp_client_server(): assert ntp_client.apply_timestep(1) is True +# TODO: Disabled until a service such as NTP can introspect to see if it's running. # Test ntp client behaviour when ntp server is unavailable. -def test_ntp_server_failure(): - network = create_ntp_network() - server: Server = network.get_node_by_hostname("ntp_server") - client: Computer = network.get_node_by_hostname("ntp_client") +# def test_ntp_server_failure(): +# network = create_ntp_network() +# server: Server = network.get_node_by_hostname("ntp_server") +# client: Computer = network.get_node_by_hostname("ntp_client") - ntp_server: NTPServer = server.software_manager.software["NTPServer"] - ntp_client: NTPClient = client.software_manager.software["NTPClient"] +# ntp_server: NTPServer = server.software_manager.software["NTPServer"] +# ntp_client: NTPClient = client.software_manager.software["NTPClient"] - assert ntp_client.operating_state == ServiceOperatingState.RUNNING +# assert ntp_client.operating_state == ServiceOperatingState.RUNNING - # Turn off ntp server. - ntp_server.stop() - assert ntp_server.operating_state == ServiceOperatingState.STOPPED - # And request a time update. - ntp_request = NTPRequest(ntp_client="192.168.1.3") - ntp_packet = NTPPacket(ntp_request=ntp_request) - ntp_client.send(payload=ntp_packet) - assert ntp_server.receive(payload=ntp_packet) is False - assert ntp_client.receive(payload=ntp_packet) is False +# # Turn off ntp server. +# ntp_server.stop() +# assert ntp_server.operating_state == ServiceOperatingState.STOPPED +# # And request a time update. +# ntp_request = NTPRequest(ntp_client="192.168.1.3") +# ntp_packet = NTPPacket(ntp_request=ntp_request) +# ntp_client.send(payload=ntp_packet) +# assert ntp_server.receive(payload=ntp_packet) is False +# assert ntp_client.receive(payload=ntp_packet) is False - # Restart ntp server. - ntp_server.start() - assert ntp_server.operating_state == ServiceOperatingState.RUNNING +# # Restart ntp server. +# ntp_server.start() +# assert ntp_server.operating_state == ServiceOperatingState.RUNNING From 243f2dd938e335c048d16f63a292e24876e7a141 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 21 Nov 2023 12:11:30 +0000 Subject: [PATCH 19/33] #2041: Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3af5c14c..a6dd0f6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ SessionManager. - DNS Services: `DNSClient` and `DNSServer` - FTP Services: `FTPClient` and `FTPServer` - HTTP Services: `WebBrowser` to simulate a web client and `WebServer` +- NTP Services: `NTPClient` and `NTPServer` ### Removed - Removed legacy simulation modules: `acl`, `common`, `environment`, `links`, `nodes`, `pol` From eb2e37429a1ccfb74f998e3a6f907c80e2a4de13 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 21 Nov 2023 17:24:24 +0000 Subject: [PATCH 20/33] #2042: Add time attribute --- .../system/services/ntp/ntp_client.py | 3 ++ .../system/test_ntp_client_server.py | 43 ++++++++++--------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index 99bc7584..81ec031c 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -1,3 +1,4 @@ +from datetime import datetime from ipaddress import IPv4Address from typing import Dict, Optional @@ -16,6 +17,7 @@ class NTPClient(Service): ip_addr: Optional[IPv4Address] = None ntp_server: Optional[IPv4Address] = None "The NTP server the client sends requests to." + time: Optional[datetime] = None def __init__(self, **kwargs): kwargs["name"] = "NTPClient" @@ -94,6 +96,7 @@ class NTPClient(Service): f"{self.name}: Received time \ update from NTP server{payload.ntp_reply.ntp_datetime}" ) + self.time = payload.ntp_reply.ntp_datetime return True def apply_timestep(self, timestep: int) -> None: diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index 8059defa..48db0cbb 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -53,38 +53,39 @@ def test_ntp_client_server(): assert ntp_server.operating_state == ServiceOperatingState.RUNNING assert ntp_client.operating_state == ServiceOperatingState.RUNNING + assert ntp_client.time is None ntp_request = NTPRequest(ntp_client="192.168.1.3") ntp_packet = NTPPacket(ntp_request=ntp_request) ntp_client.send(payload=ntp_packet) assert ntp_server.receive(payload=ntp_packet) is True assert ntp_client.receive(payload=ntp_packet) is True - + assert ntp_client.time is not None assert ntp_client.apply_timestep(1) is True -# TODO: Disabled until a service such as NTP can introspect to see if it's running. # Test ntp client behaviour when ntp server is unavailable. -# def test_ntp_server_failure(): -# network = create_ntp_network() -# server: Server = network.get_node_by_hostname("ntp_server") -# client: Computer = network.get_node_by_hostname("ntp_client") +@pytest.mark.skip(reason="NTP needs to know if underly node is RUNNING") +def test_ntp_server_failure(): + network = create_ntp_network() + server: Server = network.get_node_by_hostname("ntp_server") + client: Computer = network.get_node_by_hostname("ntp_client") -# ntp_server: NTPServer = server.software_manager.software["NTPServer"] -# ntp_client: NTPClient = client.software_manager.software["NTPClient"] + ntp_server: NTPServer = server.software_manager.software["NTPServer"] + ntp_client: NTPClient = client.software_manager.software["NTPClient"] -# assert ntp_client.operating_state == ServiceOperatingState.RUNNING + assert ntp_client.operating_state == ServiceOperatingState.RUNNING -# # Turn off ntp server. -# ntp_server.stop() -# assert ntp_server.operating_state == ServiceOperatingState.STOPPED -# # And request a time update. -# ntp_request = NTPRequest(ntp_client="192.168.1.3") -# ntp_packet = NTPPacket(ntp_request=ntp_request) -# ntp_client.send(payload=ntp_packet) -# assert ntp_server.receive(payload=ntp_packet) is False -# assert ntp_client.receive(payload=ntp_packet) is False + # Turn off ntp server. + ntp_server.stop() + assert ntp_server.operating_state == ServiceOperatingState.STOPPED + # And request a time update. + ntp_request = NTPRequest(ntp_client="192.168.1.3") + ntp_packet = NTPPacket(ntp_request=ntp_request) + ntp_client.send(payload=ntp_packet) + assert ntp_server.receive(payload=ntp_packet) is False + assert ntp_client.receive(payload=ntp_packet) is False -# # Restart ntp server. -# ntp_server.start() -# assert ntp_server.operating_state == ServiceOperatingState.RUNNING + # Restart ntp server. + ntp_server.start() + assert ntp_server.operating_state == ServiceOperatingState.RUNNING From 984d165364b7399277ccc9b3991190344683ac8a Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 21 Nov 2023 17:24:50 +0000 Subject: [PATCH 21/33] #2041: Fix long line --- src/primaite/simulator/system/services/ntp/ntp_server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/primaite/simulator/system/services/ntp/ntp_server.py b/src/primaite/simulator/system/services/ntp/ntp_server.py index 238f4f84..13bc04ee 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_server.py +++ b/src/primaite/simulator/system/services/ntp/ntp_server.py @@ -66,7 +66,8 @@ class NTPServer(Service): payload: NTPPacket = payload if payload.ntp_request.ntp_client: self.sys_log.info( - f"{self.name}: Received request for {payload.ntp_request.ntp_client} " f"from session {session_id}" + f"{self.name}: Received request for {payload.ntp_request.ntp_client} \ + from session {session_id}" ) # generate a reply with the current time time = datetime.now() From dd7c2b05f830e132a40265299dca93fe28699e6f Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 22 Nov 2023 08:54:39 +0000 Subject: [PATCH 22/33] #2041: Add RST doc --- .../system/ntp_client_server.rst | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 docs/source/simulation_components/system/ntp_client_server.rst diff --git a/docs/source/simulation_components/system/ntp_client_server.rst b/docs/source/simulation_components/system/ntp_client_server.rst new file mode 100644 index 00000000..671126fb --- /dev/null +++ b/docs/source/simulation_components/system/ntp_client_server.rst @@ -0,0 +1,54 @@ +.. only:: comment + + © Crown-owned copyright 2023, Defence Science and Technology Laboratory UK + +NTP Client Server +================= + +NTP Server +---------- +The ``NTPServer`` provides a NTP Server simulation by extending the base Service class. + +NTP Client +---------- +The ``NTPClient`` provides a NTP Client simulation by extending the base Service class. + +Key capabilities +^^^^^^^^^^^^^^^^ + +- Simulates NTP requests and NTPPacket transfer across a network +- Leverages the Service base class for install/uninstall, status tracking, etc. + +Usage +^^^^^ +- Install on a Node via the ``SoftwareManager`` to start the database service. +- Service runs on TCP port 123 by default. + +Implementation +^^^^^^^^^^^^^^ + +- NTP request and responses use a ``NTPPacket`` object +- Extends Service class for integration with ``SoftwareManager``. + +NTP Client +---------- + +The NTPClient provides a client interface for connecting to the ``NTPServer``. + +Key features +^^^^^^^^^^^^ + +- Connects to the ``NTPServer`` via the ``SoftwareManager``. + +Usage +^^^^^ + +- Install on a Node via the ``SoftwareManager`` to start the database service. +- Service runs on TCP port 123 by default. + +Implementation +^^^^^^^^^^^^^^ + +- Leverages ``SoftwareManager`` for sending payloads over the network. +- Provides easy interface for Nodes to find IP addresses via domain names. +- Extends base Service class. From 3f76e095214a4fbf44e96cb5c05913250cb2c25a Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 22 Nov 2023 14:13:50 +0000 Subject: [PATCH 23/33] #2042: remove apply_timestep() return value --- .../simulator/system/services/ntp/ntp_client.py | 9 +++------ .../system/test_ntp_client_server.py | 11 +++++++++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index 81ec031c..51df5010 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -66,6 +66,7 @@ class NTPClient(Service): :return: True if successful, False otherwise. """ + self.ip_addr = payload.ntp_request.ntp_client self.sys_log.info(f"{self.name}: Sending NTP request {payload.ntp_request.ntp_client}") return super().send( @@ -92,10 +93,7 @@ class NTPClient(Service): _LOGGER.debug(f"{payload} is not a NTPPacket") return False if payload.ntp_reply.ntp_datetime: - self.sys_log.info( - f"{self.name}: Received time \ - update from NTP server{payload.ntp_reply.ntp_datetime}" - ) + self.sys_log.info(f"{self.name}: Received time update from NTP server{payload.ntp_reply.ntp_datetime}") self.time = payload.ntp_reply.ntp_datetime return True @@ -110,13 +108,12 @@ class NTPClient(Service): :param timestep: The current timestep number. (Amount of time since simulation episode began) :type timestep: int """ + self.sys_log.info(f"{self.name} apply_timestep: IP address: {self.ip_addr}") super().apply_timestep(timestep) if self.operating_state == ServiceOperatingState.RUNNING: # request time from server ntp_request = NTPRequest(ntp_client=self.ip_addr) ntp_server_packet = NTPPacket(ntp_request=ntp_request) self.send(payload=ntp_server_packet) - return True else: self.sys_log.debug(f"{self.name} ntp client not running") - return False diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index 48db0cbb..95394e84 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -1,4 +1,5 @@ from ipaddress import IPv4Address +from time import sleep import pytest @@ -61,11 +62,17 @@ def test_ntp_client_server(): assert ntp_server.receive(payload=ntp_packet) is True assert ntp_client.receive(payload=ntp_packet) is True assert ntp_client.time is not None - assert ntp_client.apply_timestep(1) is True + first_time = ntp_client.time + sleep(0.1) + ntp_client.apply_timestep(1) # Check time advances + ntp_server.receive(payload=ntp_packet) + ntp_client.receive(payload=ntp_packet) + second_time = ntp_client.time + assert first_time != second_time # Test ntp client behaviour when ntp server is unavailable. -@pytest.mark.skip(reason="NTP needs to know if underly node is RUNNING") +@pytest.mark.skip(reason="NTP needs to know if underlying node is RUNNING") def test_ntp_server_failure(): network = create_ntp_network() server: Server = network.get_node_by_hostname("ntp_server") From 006a37d2686a8f44c20a1291d50c70581c95b3f7 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 22 Nov 2023 14:40:44 +0000 Subject: [PATCH 24/33] #2042: extract code into request_time() method. --- .../simulator/system/services/ntp/ntp_client.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index 51df5010..38ef820b 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -93,10 +93,19 @@ class NTPClient(Service): _LOGGER.debug(f"{payload} is not a NTPPacket") return False if payload.ntp_reply.ntp_datetime: - self.sys_log.info(f"{self.name}: Received time update from NTP server{payload.ntp_reply.ntp_datetime}") + self.sys_log.info( + f"{self.name}: \ + Received time update from NTP server{payload.ntp_reply.ntp_datetime}" + ) self.time = payload.ntp_reply.ntp_datetime return True + def request_time(self) -> None: + """Send request to ntp_server.""" + ntp_request = NTPRequest(ntp_client=self.ip_addr) + ntp_server_packet = NTPPacket(ntp_request=ntp_request) + self.send(payload=ntp_server_packet) + def apply_timestep(self, timestep: int) -> None: """ For each timestep request the time from the NTP server. @@ -112,8 +121,6 @@ class NTPClient(Service): super().apply_timestep(timestep) if self.operating_state == ServiceOperatingState.RUNNING: # request time from server - ntp_request = NTPRequest(ntp_client=self.ip_addr) - ntp_server_packet = NTPPacket(ntp_request=ntp_request) - self.send(payload=ntp_server_packet) + self.request_time() else: self.sys_log.debug(f"{self.name} ntp client not running") From 8584fa8f5163863795ee714a4180424b83b2cd11 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Thu, 23 Nov 2023 10:04:52 +0000 Subject: [PATCH 25/33] # 2041: Minor test changes --- src/primaite/simulator/system/services/ntp/ntp_client.py | 4 ++-- src/primaite/simulator/system/services/ntp/ntp_server.py | 4 ++-- tests/integration_tests/system/test_ntp_client_server.py | 4 +++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index 38ef820b..2b2c725a 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -100,9 +100,9 @@ class NTPClient(Service): self.time = payload.ntp_reply.ntp_datetime return True - def request_time(self) -> None: + def request_time(self, ip_address: IPv4Address = ip_addr) -> None: """Send request to ntp_server.""" - ntp_request = NTPRequest(ntp_client=self.ip_addr) + ntp_request = NTPRequest(ntp_client=ip_address) ntp_server_packet = NTPPacket(ntp_request=ntp_request) self.send(payload=ntp_server_packet) diff --git a/src/primaite/simulator/system/services/ntp/ntp_server.py b/src/primaite/simulator/system/services/ntp/ntp_server.py index 13bc04ee..6d76c1ed 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_server.py +++ b/src/primaite/simulator/system/services/ntp/ntp_server.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Any, Dict, Optional +from typing import Dict, Optional from primaite import getLogger from primaite.simulator.network.protocols.ntp import NTPPacket @@ -46,7 +46,7 @@ class NTPServer(Service): def receive( self, - payload: Any, + payload: NTPPacket, session_id: Optional[str] = None, **kwargs, ) -> bool: diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index 95394e84..54e54a5b 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -58,7 +58,9 @@ def test_ntp_client_server(): ntp_request = NTPRequest(ntp_client="192.168.1.3") ntp_packet = NTPPacket(ntp_request=ntp_request) - ntp_client.send(payload=ntp_packet) + # ntp_client.send(payload=ntp_packet) + ntp_client.request_time("192.168.1.3") + assert ntp_server.receive(payload=ntp_packet) is True assert ntp_client.receive(payload=ntp_packet) is True assert ntp_client.time is not None From 87dde6ee0bfcad3e3993b1e968de8b63790a2a49 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Thu, 23 Nov 2023 15:55:58 +0000 Subject: [PATCH 26/33] #2042: Test tidying changes. --- .../system/services/ntp/ntp_client.py | 16 ++++++++++++++-- .../system/test_ntp_client_server.py | 19 ++++++++++++------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index 2b2c725a..c75e639d 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -15,6 +15,7 @@ class NTPClient(Service): """Represents a NTP client as a service.""" ip_addr: Optional[IPv4Address] = None + "The IP address of the NTP client" ntp_server: Optional[IPv4Address] = None "The NTP server the client sends requests to." time: Optional[datetime] = None @@ -26,6 +27,17 @@ class NTPClient(Service): super().__init__(**kwargs) self.start() + def configure(self, ntp_server_ip_address: IPv4Address, ntp_client_ip_address: IPv4Address) -> None: + """ + Set the IP address for the NTP server. + + :param ntp_server_ip_address: IPv4 address of NTP server. + :param ntp_client_ip_Address: IPv4 address of NTP client. + """ + self.ip_addr = ntp_client_ip_address + self.ntp_server = ntp_server_ip_address + self.sys_log.info(f"{self.name}: ip_addr: {self.ip_addr}, ntp_server: {self.ntp_server}") + def describe_state(self) -> Dict: """ Describes the current state of the software. @@ -100,9 +112,9 @@ class NTPClient(Service): self.time = payload.ntp_reply.ntp_datetime return True - def request_time(self, ip_address: IPv4Address = ip_addr) -> None: + def request_time(self) -> None: """Send request to ntp_server.""" - ntp_request = NTPRequest(ntp_client=ip_address) + ntp_request = NTPRequest(ntp_client=self.ip_addr) ntp_server_packet = NTPPacket(ntp_request=ntp_request) self.send(payload=ntp_server_packet) diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index 54e54a5b..dec6c0f7 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -36,6 +36,7 @@ def create_ntp_network() -> Network: ) ntp_client.power_on() ntp_client.software_manager.install(NTPClient) + network.connect(endpoint_b=ntp_server.ethernet_port[1], endpoint_a=ntp_client.ethernet_port[1]) return network @@ -54,21 +55,25 @@ def test_ntp_client_server(): assert ntp_server.operating_state == ServiceOperatingState.RUNNING assert ntp_client.operating_state == ServiceOperatingState.RUNNING + ntp_client.configure( + ntp_server_ip_address=IPv4Address("192.168.1.2"), ntp_client_ip_address=IPv4Address("192.168.1.3") + ) + assert ntp_client.time is None - ntp_request = NTPRequest(ntp_client="192.168.1.3") - ntp_packet = NTPPacket(ntp_request=ntp_request) + # ntp_request = NTPRequest(ntp_client="192.168.1.3") + # ntp_packet = NTPPacket(ntp_request=ntp_request) # ntp_client.send(payload=ntp_packet) - ntp_client.request_time("192.168.1.3") + ntp_client.request_time() - assert ntp_server.receive(payload=ntp_packet) is True - assert ntp_client.receive(payload=ntp_packet) is True + # assert ntp_server.receive(payload=ntp_packet) is True + # assert ntp_client.receive(payload=ntp_packet) is True assert ntp_client.time is not None first_time = ntp_client.time sleep(0.1) ntp_client.apply_timestep(1) # Check time advances - ntp_server.receive(payload=ntp_packet) - ntp_client.receive(payload=ntp_packet) + # ntp_server.receive(payload=ntp_packet) + # ntp_client.receive(payload=ntp_packet) second_time = ntp_client.time assert first_time != second_time From 12ede2329b48e3225acef5fb86030a54eacfa91d Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 6 Dec 2023 16:41:10 +0000 Subject: [PATCH 27/33] 2041: Add network config and pytest fixture --- .../system/test_ntp_client_server.py | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index dec6c0f7..ed5e6962 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -1,5 +1,6 @@ from ipaddress import IPv4Address from time import sleep +from typing import Tuple import pytest @@ -14,7 +15,8 @@ from primaite.simulator.system.services.service import ServiceOperatingState # Create simple network for testing -def create_ntp_network() -> Network: +@pytest.fixture(scope="function") +def create_ntp_network(client_server) -> Tuple[NTPClient, Computer, NTPServer, Server]: """ +------------+ +------------+ | ntp | | ntp | @@ -23,32 +25,26 @@ def create_ntp_network() -> Network: +------------+ +------------+ """ + client, server = client_server - network = Network() - ntp_server = Server( - hostname="ntp_server", ip_address="192.168.1.2", subnet_mask="255.255.255.0", default_gateway="192.168.1.1" - ) - ntp_server.power_on() - ntp_server.software_manager.install(NTPServer) + server.power_on() + server.software_manager.install(NTPServer) + ntp_server: NTPServer = server.software_manager.software.get("NTPServer") + ntp_server.start() - ntp_client = Computer( - hostname="ntp_client", ip_address="192.168.1.3", subnet_mask="255.255.255.0", default_gateway="192.168.1.1" - ) - ntp_client.power_on() - ntp_client.software_manager.install(NTPClient) + client.power_on() + client.software_manager.install(NTPClient) + ntp_client: NTPClient = client.software_manager.software.get("NTPClient") + ntp_client.start() - network.connect(endpoint_b=ntp_server.ethernet_port[1], endpoint_a=ntp_client.ethernet_port[1]) - - return network + return ntp_client, client, ntp_server, server # Define one node to be an NTP server and another node to be a NTP Client. -def test_ntp_client_server(): - network = create_ntp_network() - server: Server = network.get_node_by_hostname("ntp_server") - client: Computer = network.get_node_by_hostname("ntp_client") +def test_ntp_client_server(create_ntp_network): + ntp_client, client, ntp_server, server = create_ntp_network ntp_server: NTPServer = server.software_manager.software["NTPServer"] ntp_client: NTPClient = client.software_manager.software["NTPClient"] @@ -56,24 +52,15 @@ def test_ntp_client_server(): assert ntp_server.operating_state == ServiceOperatingState.RUNNING assert ntp_client.operating_state == ServiceOperatingState.RUNNING ntp_client.configure( - ntp_server_ip_address=IPv4Address("192.168.1.2"), ntp_client_ip_address=IPv4Address("192.168.1.3") + ntp_server_ip_address=IPv4Address("192.168.0.2"), ntp_client_ip_address=IPv4Address("192.168.0.1") ) assert ntp_client.time is None - - # ntp_request = NTPRequest(ntp_client="192.168.1.3") - # ntp_packet = NTPPacket(ntp_request=ntp_request) - # ntp_client.send(payload=ntp_packet) ntp_client.request_time() - - # assert ntp_server.receive(payload=ntp_packet) is True - # assert ntp_client.receive(payload=ntp_packet) is True assert ntp_client.time is not None first_time = ntp_client.time sleep(0.1) ntp_client.apply_timestep(1) # Check time advances - # ntp_server.receive(payload=ntp_packet) - # ntp_client.receive(payload=ntp_packet) second_time = ntp_client.time assert first_time != second_time From 50a6e17fabfd6c20eb0f5ab63b01e7f3377144b5 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 6 Dec 2023 16:42:28 +0000 Subject: [PATCH 28/33] 2041: Make NTP work with TCP transport layer --- src/primaite/game/agent/actions.py | 2 +- src/primaite/game/game.py | 4 ++++ .../simulator/system/services/ntp/ntp_client.py | 10 +++++----- .../simulator/system/services/ntp/ntp_server.py | 3 ++- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/primaite/game/agent/actions.py b/src/primaite/game/agent/actions.py index 8eed3ba4..893b12b4 100644 --- a/src/primaite/game/agent/actions.py +++ b/src/primaite/game/agent/actions.py @@ -601,7 +601,7 @@ class ActionManager: max_nics_per_node: int = 8, # allows calculating shape max_acl_rules: int = 10, # allows calculating shape protocols: List[str] = ["TCP", "UDP", "ICMP"], # allow mapping index to protocol - ports: List[str] = ["HTTP", "DNS", "ARP", "FTP"], # allow mapping index to port + ports: List[str] = ["HTTP", "DNS", "ARP", "FTP", "NTP"], # allow mapping index to port ip_address_list: Optional[List[str]] = None, # to allow us to map an index to an ip address. act_map: Optional[Dict[int, Dict]] = None, # allows restricting set of possible actions ) -> None: diff --git a/src/primaite/game/game.py b/src/primaite/game/game.py index a36cbea9..edfe058d 100644 --- a/src/primaite/game/game.py +++ b/src/primaite/game/game.py @@ -25,6 +25,8 @@ 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.ftp.ftp_client import FTPClient from primaite.simulator.system.services.ftp.ftp_server import FTPServer +from primaite.simulator.system.services.ntp.ntp_client import NTPClient +from primaite.simulator.system.services.ntp.ntp_server import NTPServer from primaite.simulator.system.services.red_services.data_manipulation_bot import DataManipulationBot from primaite.simulator.system.services.web_server.web_server import WebServer @@ -257,6 +259,8 @@ class PrimaiteGame: "WebServer": WebServer, "FTPClient": FTPClient, "FTPServer": FTPServer, + "NTPClient": NTPClient, + "NTPServer": NTPServer, } if service_type in service_types_mapping: _LOGGER.debug(f"installing {service_type} on node {new_node.hostname}") diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index c75e639d..f9cf29d4 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -23,7 +23,7 @@ class NTPClient(Service): def __init__(self, **kwargs): kwargs["name"] = "NTPClient" kwargs["port"] = Port.NTP - kwargs["protocol"] = IPProtocol.UDP + kwargs["protocol"] = IPProtocol.TCP super().__init__(**kwargs) self.start() @@ -65,7 +65,7 @@ class NTPClient(Service): self, payload: NTPPacket, session_id: Optional[str] = None, - dest_ip_address: IPv4Address = ntp_server, + dest_ip_address: IPv4Address = None, dest_port: [Port] = Port.NTP, **kwargs, ) -> bool: @@ -79,8 +79,6 @@ class NTPClient(Service): :return: True if successful, False otherwise. """ self.ip_addr = payload.ntp_request.ntp_client - self.sys_log.info(f"{self.name}: Sending NTP request {payload.ntp_request.ntp_client}") - return super().send( payload=payload, dest_ip_address=dest_ip_address, @@ -101,6 +99,8 @@ class NTPClient(Service): :param session_id: The Session ID the payload is to originate from. Optional. :return: True if successful, False otherwise. """ + self.sys_log.info(f"{self.name}: Receiving NTP request from {payload.ntp_request.ntp_client}") + if not (isinstance(payload, NTPPacket) and payload.ntp_request.ntp_client): _LOGGER.debug(f"{payload} is not a NTPPacket") return False @@ -116,7 +116,7 @@ class NTPClient(Service): """Send request to ntp_server.""" ntp_request = NTPRequest(ntp_client=self.ip_addr) ntp_server_packet = NTPPacket(ntp_request=ntp_request) - self.send(payload=ntp_server_packet) + self.send(payload=ntp_server_packet, dest_ip_address=self.ntp_server) def apply_timestep(self, timestep: int) -> None: """ diff --git a/src/primaite/simulator/system/services/ntp/ntp_server.py b/src/primaite/simulator/system/services/ntp/ntp_server.py index 6d76c1ed..400c397f 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_server.py +++ b/src/primaite/simulator/system/services/ntp/ntp_server.py @@ -16,7 +16,7 @@ class NTPServer(Service): def __init__(self, **kwargs): kwargs["name"] = "NTPServer" kwargs["port"] = Port.NTP - kwargs["protocol"] = IPProtocol.UDP + kwargs["protocol"] = IPProtocol.TCP super().__init__(**kwargs) self.start() @@ -60,6 +60,7 @@ class NTPServer(Service): :return: True if valid NTP request else False. """ + self.sys_log.info(f"{self.name} received request from {payload.ntp_request.ntp_client}") if not (isinstance(payload, NTPPacket) and payload.ntp_request.ntp_client): _LOGGER.debug(f"{payload} is not a NTPPacket") return False From 44ada941e62cb1be63a832d6a9578349e6a1bec7 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Thu, 7 Dec 2023 14:22:27 +0000 Subject: [PATCH 29/33] 2041: Reinstate test for ntp_server failure --- .../system/test_ntp_client_server.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index ed5e6962..97b2fe30 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -66,27 +66,27 @@ def test_ntp_client_server(create_ntp_network): # Test ntp client behaviour when ntp server is unavailable. -@pytest.mark.skip(reason="NTP needs to know if underlying node is RUNNING") -def test_ntp_server_failure(): - network = create_ntp_network() - server: Server = network.get_node_by_hostname("ntp_server") - client: Computer = network.get_node_by_hostname("ntp_client") +def test_ntp_server_failure(create_ntp_network): + ntp_client, client, ntp_server, server = create_ntp_network ntp_server: NTPServer = server.software_manager.software["NTPServer"] ntp_client: NTPClient = client.software_manager.software["NTPClient"] assert ntp_client.operating_state == ServiceOperatingState.RUNNING + assert ntp_client.operating_state == ServiceOperatingState.RUNNING + ntp_client.configure( + ntp_server_ip_address=IPv4Address("192.168.0.2"), ntp_client_ip_address=IPv4Address("192.168.0.1") + ) # Turn off ntp server. ntp_server.stop() assert ntp_server.operating_state == ServiceOperatingState.STOPPED # And request a time update. - ntp_request = NTPRequest(ntp_client="192.168.1.3") - ntp_packet = NTPPacket(ntp_request=ntp_request) - ntp_client.send(payload=ntp_packet) - assert ntp_server.receive(payload=ntp_packet) is False - assert ntp_client.receive(payload=ntp_packet) is False + ntp_client.request_time() + assert ntp_client.time is None # Restart ntp server. ntp_server.start() assert ntp_server.operating_state == ServiceOperatingState.RUNNING + ntp_client.request_time() + assert ntp_client.time is not None From e620771c8d5524e481f2a1d4d291076fac0dd633 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 12 Dec 2023 17:08:11 +0000 Subject: [PATCH 30/33] 2041: Remove IP address from NTP client (review comment) --- .../system/services/ntp/ntp_client.py | 16 ++++-------- .../system/services/ntp/ntp_server.py | 25 ++++++------------- .../system/test_ntp_client_server.py | 8 ++---- 3 files changed, 15 insertions(+), 34 deletions(-) diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index f9cf29d4..e3cd21cf 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -14,8 +14,6 @@ _LOGGER = getLogger(__name__) class NTPClient(Service): """Represents a NTP client as a service.""" - ip_addr: Optional[IPv4Address] = None - "The IP address of the NTP client" ntp_server: Optional[IPv4Address] = None "The NTP server the client sends requests to." time: Optional[datetime] = None @@ -27,16 +25,15 @@ class NTPClient(Service): super().__init__(**kwargs) self.start() - def configure(self, ntp_server_ip_address: IPv4Address, ntp_client_ip_address: IPv4Address) -> None: + def configure(self, ntp_server_ip_address: IPv4Address) -> None: """ Set the IP address for the NTP server. :param ntp_server_ip_address: IPv4 address of NTP server. :param ntp_client_ip_Address: IPv4 address of NTP client. """ - self.ip_addr = ntp_client_ip_address self.ntp_server = ntp_server_ip_address - self.sys_log.info(f"{self.name}: ip_addr: {self.ip_addr}, ntp_server: {self.ntp_server}") + self.sys_log.info(f"{self.name}: ntp_server: {self.ntp_server}") def describe_state(self) -> Dict: """ @@ -78,7 +75,6 @@ class NTPClient(Service): :return: True if successful, False otherwise. """ - self.ip_addr = payload.ntp_request.ntp_client return super().send( payload=payload, dest_ip_address=dest_ip_address, @@ -99,9 +95,7 @@ class NTPClient(Service): :param session_id: The Session ID the payload is to originate from. Optional. :return: True if successful, False otherwise. """ - self.sys_log.info(f"{self.name}: Receiving NTP request from {payload.ntp_request.ntp_client}") - - if not (isinstance(payload, NTPPacket) and payload.ntp_request.ntp_client): + if not isinstance(payload, NTPPacket): _LOGGER.debug(f"{payload} is not a NTPPacket") return False if payload.ntp_reply.ntp_datetime: @@ -114,7 +108,7 @@ class NTPClient(Service): def request_time(self) -> None: """Send request to ntp_server.""" - ntp_request = NTPRequest(ntp_client=self.ip_addr) + ntp_request = NTPRequest() ntp_server_packet = NTPPacket(ntp_request=ntp_request) self.send(payload=ntp_server_packet, dest_ip_address=self.ntp_server) @@ -129,7 +123,7 @@ class NTPClient(Service): :param timestep: The current timestep number. (Amount of time since simulation episode began) :type timestep: int """ - self.sys_log.info(f"{self.name} apply_timestep: IP address: {self.ip_addr}") + self.sys_log.info(f"{self.name} apply_timestep") super().apply_timestep(timestep) if self.operating_state == ServiceOperatingState.RUNNING: # request time from server diff --git a/src/primaite/simulator/system/services/ntp/ntp_server.py b/src/primaite/simulator/system/services/ntp/ntp_server.py index 400c397f..0a66384a 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_server.py +++ b/src/primaite/simulator/system/services/ntp/ntp_server.py @@ -60,23 +60,14 @@ class NTPServer(Service): :return: True if valid NTP request else False. """ - self.sys_log.info(f"{self.name} received request from {payload.ntp_request.ntp_client}") - if not (isinstance(payload, NTPPacket) and payload.ntp_request.ntp_client): + if not (isinstance(payload, NTPPacket)): _LOGGER.debug(f"{payload} is not a NTPPacket") return False payload: NTPPacket = payload - if payload.ntp_request.ntp_client: - self.sys_log.info( - f"{self.name}: Received request for {payload.ntp_request.ntp_client} \ - from session {session_id}" - ) - # generate a reply with the current time - time = datetime.now() - payload = payload.generate_reply(time) - self.sys_log.info( - f"{self.name}: Responding to NTP request for {payload.ntp_request.ntp_client} " - f"with current time: {time}" - ) - # send reply - self.send(payload, session_id) - return True + + # generate a reply with the current time + time = datetime.now() + payload = payload.generate_reply(time) + # send reply + self.send(payload, session_id) + return True diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index 97b2fe30..c30fd5bc 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -51,9 +51,7 @@ def test_ntp_client_server(create_ntp_network): assert ntp_server.operating_state == ServiceOperatingState.RUNNING assert ntp_client.operating_state == ServiceOperatingState.RUNNING - ntp_client.configure( - ntp_server_ip_address=IPv4Address("192.168.0.2"), ntp_client_ip_address=IPv4Address("192.168.0.1") - ) + ntp_client.configure(ntp_server_ip_address=IPv4Address("192.168.0.2")) assert ntp_client.time is None ntp_client.request_time() @@ -74,9 +72,7 @@ def test_ntp_server_failure(create_ntp_network): assert ntp_client.operating_state == ServiceOperatingState.RUNNING assert ntp_client.operating_state == ServiceOperatingState.RUNNING - ntp_client.configure( - ntp_server_ip_address=IPv4Address("192.168.0.2"), ntp_client_ip_address=IPv4Address("192.168.0.1") - ) + ntp_client.configure(ntp_server_ip_address=IPv4Address("192.168.0.2")) # Turn off ntp server. ntp_server.stop() From f7b5c8ae2fda6cd7311bb4052bdfd515f6402e4f Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 13 Dec 2023 10:34:52 +0000 Subject: [PATCH 31/33] 2041: Remove NTPRequest class (review comment) --- src/primaite/simulator/network/protocols/ntp.py | 9 --------- src/primaite/simulator/system/services/ntp/ntp_client.py | 6 +++--- tests/integration_tests/system/test_ntp_client_server.py | 2 +- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/primaite/simulator/network/protocols/ntp.py b/src/primaite/simulator/network/protocols/ntp.py index df5ce0c1..55353265 100644 --- a/src/primaite/simulator/network/protocols/ntp.py +++ b/src/primaite/simulator/network/protocols/ntp.py @@ -1,7 +1,6 @@ from __future__ import annotations from datetime import datetime -from ipaddress import IPv4Address from typing import Optional from pydantic import BaseModel @@ -9,12 +8,6 @@ from pydantic import BaseModel from primaite.simulator.network.protocols.packet import DataPacket -class NTPRequest(BaseModel): - """Represents a NTP Request packet.""" - - ntp_client: Optional[IPv4Address] = None - - class NTPReply(BaseModel): """Represents a NTP Reply packet.""" @@ -30,8 +23,6 @@ class NTPPacket(DataPacket): :param ntp_reply: NTPReply packet from NTP Server. """ - ntp_request: NTPRequest - "NTP Request packet sent by NTP Client." ntp_reply: Optional[NTPReply] = None def generate_reply(self, ntp_server_time: datetime) -> NTPPacket: diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index e3cd21cf..e8c3d0cb 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -3,7 +3,7 @@ from ipaddress import IPv4Address from typing import Dict, Optional from primaite import getLogger -from primaite.simulator.network.protocols.ntp import NTPPacket, NTPRequest +from primaite.simulator.network.protocols.ntp import NTPPacket from primaite.simulator.network.transmission.network_layer import IPProtocol from primaite.simulator.network.transmission.transport_layer import Port from primaite.simulator.system.services.service import Service, ServiceOperatingState @@ -108,8 +108,8 @@ class NTPClient(Service): def request_time(self) -> None: """Send request to ntp_server.""" - ntp_request = NTPRequest() - ntp_server_packet = NTPPacket(ntp_request=ntp_request) + ntp_server_packet = NTPPacket() + self.send(payload=ntp_server_packet, dest_ip_address=self.ntp_server) def apply_timestep(self, timestep: int) -> None: diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index c30fd5bc..d58e3372 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -7,7 +7,7 @@ import pytest from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.nodes.computer import Computer from primaite.simulator.network.hardware.nodes.server import Server -from primaite.simulator.network.protocols.ntp import NTPPacket, NTPRequest +from primaite.simulator.network.protocols.ntp import NTPPacket from primaite.simulator.system.services.ntp.ntp_client import NTPClient from primaite.simulator.system.services.ntp.ntp_server import NTPServer from primaite.simulator.system.services.service import ServiceOperatingState From 0cfd525ab8c2a1a5c4c4ef1c05ec14119f1d6a39 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Fri, 15 Dec 2023 10:14:35 +0000 Subject: [PATCH 32/33] 2041: change comparison operator in test --- tests/integration_tests/system/test_ntp_client_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index d58e3372..f626322f 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -60,7 +60,7 @@ def test_ntp_client_server(create_ntp_network): sleep(0.1) ntp_client.apply_timestep(1) # Check time advances second_time = ntp_client.time - assert first_time != second_time + assert first_time < second_time # Test ntp client behaviour when ntp server is unavailable. From 2d892d4a5acd88e2031ddffa3d336e461169e02c Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Fri, 15 Dec 2023 10:52:46 +0000 Subject: [PATCH 33/33] 2041: Tidy up test comments --- tests/integration_tests/system/test_ntp_client_server.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index f626322f..b7839479 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -13,6 +13,7 @@ from primaite.simulator.system.services.ntp.ntp_server import NTPServer from primaite.simulator.system.services.service import ServiceOperatingState # Create simple network for testing +# Define one node to be an NTP server and another node to be a NTP Client. @pytest.fixture(scope="function") @@ -40,9 +41,6 @@ def create_ntp_network(client_server) -> Tuple[NTPClient, Computer, NTPServer, S return ntp_client, client, ntp_server, server -# Define one node to be an NTP server and another node to be a NTP Client. - - def test_ntp_client_server(create_ntp_network): ntp_client, client, ntp_server, server = create_ntp_network