Merge branch 'dev' into bugfix/2299-check_hash_function_corrupts_files_and_folders

This commit is contained in:
Nick Todd
2024-04-30 09:17:40 +01:00
5 changed files with 268 additions and 6 deletions

View File

@@ -0,0 +1,221 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Requests and Responses\n",
"\n",
"Agents interact with the PrimAITE simulation via the Request system.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Sending a request"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's set up a minimal network simulation and send some requests to see how it works."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState\n",
"from primaite.simulator.network.hardware.nodes.host.host_node import HostNode\n",
"from primaite.simulator.sim_container import Simulation\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sim = Simulation()\n",
"sim.network.add_node(\n",
" HostNode(\n",
" hostname=\"client\",\n",
" ip_address='10.0.0.1',\n",
" subnet_mask='255.255.255.0',\n",
" operating_state=NodeOperatingState.ON)\n",
")\n",
"client = sim.network.get_node_by_hostname('client')\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A request is structured in a similar way to a command line interface - a list of strings with positional args. It's also possible to supply an optional `context` dictionary. We will craft a request that stops the pre-installed DNSClient service on the client node.\n",
"\n",
"First let's verify that the DNS Client is running on the client.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"client.software_manager.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Send a request to the simulator to stop the DNSClient."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response = sim.apply_request(\n",
" request=[\"network\", \"node\", \"client\", \"service\", \"DNSClient\", \"stop\"],\n",
" context={}\n",
" )\n",
"print(response)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"The request returns a `RequestResponse` object which tells us that the request was successfully executed. Let's verify that the DNS client is in a stopped state now."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(f\"DNS Client state: {client.software_manager.software.get('DNSClient').operating_state.name}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Unreachable requests"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If we attempt to send a request to something that doesn't exist, we will get an unreachable request status."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response = sim.apply_request(\n",
" request=[\"network\", \"node\", \"client\", \"service\", \"NonExistentApplication\", \"stop\"],\n",
" context={}\n",
" )\n",
"print(response)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Failed requests\n",
"\n",
"Sometimes requests cannot be executed by the simulation. For example if we turn off the client node, we cannot execute the software that is running on it."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response = sim.apply_request(\n",
" request = [\"network\", \"node\", \"client\", \"shutdown\"],\n",
" context = {}\n",
")\n",
"print(response)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We need to apply timestep a few times for the client to go from `SHUTTING_DOWN` to `OFF` state."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(f\"client is in state: {client.operating_state.name}\")\n",
"sim.apply_timestep(1)\n",
"sim.apply_timestep(2)\n",
"sim.apply_timestep(3)\n",
"sim.apply_timestep(4)\n",
"print(f\"client is in state: {client.operating_state.name}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, if we try to start the DNSClient back up, we get a failure because we cannot start software on a node that is turned off."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response = sim.apply_request(\n",
" request=[\"network\", \"node\", \"client\", \"service\", \"DNSClient\", \"start\"],\n",
" context={}\n",
" )\n",
"print(response)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -1,5 +1,6 @@
# flake8: noqa
"""Core of the PrimAITE Simulator."""
import warnings
from abc import abstractmethod
from typing import Callable, Dict, List, Literal, Optional, Union
from uuid import uuid4
@@ -26,6 +27,12 @@ class RequestPermissionValidator(BaseModel):
"""Use the request and context parameters to decide whether the request should be permitted."""
pass
@property
@abstractmethod
def fail_message(self) -> str:
"""Message that is reported when a request is rejected by this validator."""
return "request rejected"
class AllowAllValidator(RequestPermissionValidator):
"""Always allows the request."""
@@ -34,6 +41,16 @@ class AllowAllValidator(RequestPermissionValidator):
"""Always allow the request."""
return True
@property
def fail_message(self) -> str:
"""
Message that is reported when a request is rejected by this validator.
This method should really never be called because this validator never rejects requests.
"""
warnings.warn("Something went wrong - AllowAllValidator rejected a request.")
return super().fail_message
class RequestType(BaseModel):
"""
@@ -99,7 +116,7 @@ class RequestManager(BaseModel):
if not request_type.validator(request_options, context):
_LOGGER.debug(f"Request {request} was denied due to insufficient permissions")
return RequestResponse(status="failure", data={"reason": "request validation failed"})
return RequestResponse(status="failure", data={"reason": request_type.validator.fail_message})
return request_type.func(request_options, context)

View File

@@ -57,6 +57,11 @@ class GroupMembershipValidator(RequestPermissionValidator):
return True
return False
@property
def fail_message(self) -> str:
"""Message that is reported when a request is rejected by this validator."""
return "User does not belong to group"
class DomainController(SimComponent):
"""Main object for controlling the domain."""

View File

@@ -798,6 +798,11 @@ class Node(SimComponent):
"""Return whether the node is on or off."""
return self.node.operating_state == NodeOperatingState.ON
@property
def fail_message(self) -> str:
"""Message that is reported when a request is rejected by this validator."""
return f"Cannot perform request on node '{self.node.hostname}' because it is not turned on."
def _init_request_manager(self) -> RequestManager:
"""
Initialise the request manager.