2024-03-29 14:14:03 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from typing import Dict, Iterable, List, Optional
|
2024-03-08 14:08:35 +00:00
|
|
|
|
|
|
|
|
from gymnasium import spaces
|
2024-03-29 14:14:03 +00:00
|
|
|
from gymnasium.core import ObsType
|
2024-03-08 14:08:35 +00:00
|
|
|
|
|
|
|
|
from primaite import getLogger
|
2024-03-29 14:14:03 +00:00
|
|
|
from primaite.game.agent.observations.observations import AbstractObservation, WhereType
|
2024-03-08 14:08:35 +00:00
|
|
|
from primaite.game.agent.utils import access_from_nested_dict, NOT_PRESENT_IN_STATE
|
|
|
|
|
|
|
|
|
|
_LOGGER = getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
2024-03-29 14:14:03 +00:00
|
|
|
class FileObservation(AbstractObservation, identifier="FILE"):
|
|
|
|
|
"""File observation, provides status information about a file within the simulation environment."""
|
|
|
|
|
|
|
|
|
|
class ConfigSchema(AbstractObservation.ConfigSchema):
|
|
|
|
|
"""Configuration schema for FileObservation."""
|
2024-03-08 14:08:35 +00:00
|
|
|
|
2024-03-29 14:14:03 +00:00
|
|
|
file_name: str
|
|
|
|
|
"""Name of the file, used for querying simulation state dictionary."""
|
|
|
|
|
include_num_access: Optional[bool] = None
|
|
|
|
|
"""Whether to include the number of accesses to the file in the observation."""
|
2024-03-08 14:08:35 +00:00
|
|
|
|
2024-03-29 14:14:03 +00:00
|
|
|
def __init__(self, where: WhereType, include_num_access: bool) -> None:
|
|
|
|
|
"""
|
2024-03-31 17:31:10 +01:00
|
|
|
Initialise a file observation instance.
|
2024-03-29 14:14:03 +00:00
|
|
|
|
|
|
|
|
:param where: Where in the simulation state dictionary to find the relevant information for this file.
|
|
|
|
|
A typical location for a file might be
|
|
|
|
|
['network', 'nodes', <node_hostname>, 'file_system', 'folder', <folder_name>, 'files', <file_name>].
|
|
|
|
|
:type where: WhereType
|
|
|
|
|
:param include_num_access: Whether to include the number of accesses to the file in the observation.
|
|
|
|
|
:type include_num_access: bool
|
2024-03-08 14:08:35 +00:00
|
|
|
"""
|
2024-03-29 14:14:03 +00:00
|
|
|
self.where: WhereType = where
|
|
|
|
|
self.include_num_access: bool = include_num_access
|
2024-03-08 14:08:35 +00:00
|
|
|
|
2024-03-29 14:14:03 +00:00
|
|
|
self.default_observation: ObsType = {"health_status": 0}
|
|
|
|
|
if self.include_num_access:
|
|
|
|
|
self.default_observation["num_access"] = 0
|
2024-03-08 14:08:35 +00:00
|
|
|
|
2024-03-29 14:14:03 +00:00
|
|
|
def observe(self, state: Dict) -> ObsType:
|
2024-03-08 14:08:35 +00:00
|
|
|
"""
|
2024-03-29 14:14:03 +00:00
|
|
|
Generate observation based on the current state of the simulation.
|
2024-03-08 14:08:35 +00:00
|
|
|
|
2024-03-29 14:14:03 +00:00
|
|
|
:param state: Simulation state dictionary.
|
2024-03-08 14:08:35 +00:00
|
|
|
:type state: Dict
|
2024-03-29 14:14:03 +00:00
|
|
|
:return: Observation containing the health status of the file and optionally the number of accesses.
|
|
|
|
|
:rtype: ObsType
|
2024-03-08 14:08:35 +00:00
|
|
|
"""
|
|
|
|
|
file_state = access_from_nested_dict(state, self.where)
|
|
|
|
|
if file_state is NOT_PRESENT_IN_STATE:
|
|
|
|
|
return self.default_observation
|
2024-03-29 14:14:03 +00:00
|
|
|
obs = {"health_status": file_state["visible_status"]}
|
|
|
|
|
if self.include_num_access:
|
|
|
|
|
obs["num_access"] = file_state["num_access"]
|
|
|
|
|
# raise NotImplementedError("TODO: need to fix num_access to use thresholds instead of raw value.")
|
|
|
|
|
return obs
|
2024-03-08 14:08:35 +00:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def space(self) -> spaces.Space:
|
2024-03-29 14:14:03 +00:00
|
|
|
"""
|
|
|
|
|
Gymnasium space object describing the observation space shape.
|
2024-03-08 14:08:35 +00:00
|
|
|
|
2024-03-29 14:14:03 +00:00
|
|
|
:return: Gymnasium space representing the observation space for file status.
|
2024-03-08 14:08:35 +00:00
|
|
|
:rtype: spaces.Space
|
|
|
|
|
"""
|
2024-03-29 14:14:03 +00:00
|
|
|
space = {"health_status": spaces.Discrete(6)}
|
|
|
|
|
if self.include_num_access:
|
|
|
|
|
space["num_access"] = spaces.Discrete(4)
|
|
|
|
|
return spaces.Dict(space)
|
2024-03-08 14:08:35 +00:00
|
|
|
|
|
|
|
|
@classmethod
|
2024-03-29 14:14:03 +00:00
|
|
|
def from_config(cls, config: ConfigSchema, parent_where: WhereType = []) -> FileObservation:
|
|
|
|
|
"""
|
|
|
|
|
Create a file observation from a configuration schema.
|
|
|
|
|
|
|
|
|
|
:param config: Configuration schema containing the necessary information for the file observation.
|
|
|
|
|
:type config: ConfigSchema
|
|
|
|
|
:param parent_where: Where in the simulation state dictionary to find the information about this file's
|
|
|
|
|
parent node. A typical location for a node might be ['network', 'nodes', <node_hostname>].
|
|
|
|
|
:type parent_where: WhereType, optional
|
|
|
|
|
:return: Constructed file observation instance.
|
|
|
|
|
:rtype: FileObservation
|
2024-03-08 14:08:35 +00:00
|
|
|
"""
|
2024-03-29 14:14:03 +00:00
|
|
|
return cls(where=parent_where + ["files", config.file_name], include_num_access=config.include_num_access)
|
2024-03-08 14:08:35 +00:00
|
|
|
|
|
|
|
|
|
2024-03-29 14:14:03 +00:00
|
|
|
class FolderObservation(AbstractObservation, identifier="FOLDER"):
|
|
|
|
|
"""Folder observation, provides status information about a folder within the simulation environment."""
|
|
|
|
|
|
|
|
|
|
class ConfigSchema(AbstractObservation.ConfigSchema):
|
|
|
|
|
"""Configuration schema for FolderObservation."""
|
|
|
|
|
|
|
|
|
|
folder_name: str
|
|
|
|
|
"""Name of the folder, used for querying simulation state dictionary."""
|
|
|
|
|
files: List[FileObservation.ConfigSchema] = []
|
|
|
|
|
"""List of file configurations within the folder."""
|
|
|
|
|
num_files: Optional[int] = None
|
|
|
|
|
"""Number of spaces for file observations in this folder."""
|
|
|
|
|
include_num_access: Optional[bool] = None
|
|
|
|
|
"""Whether files in this folder should include the number of accesses in their observation."""
|
2024-03-08 14:08:35 +00:00
|
|
|
|
|
|
|
|
def __init__(
|
2024-03-29 14:14:03 +00:00
|
|
|
self, where: WhereType, files: Iterable[FileObservation], num_files: int, include_num_access: bool
|
2024-03-08 14:08:35 +00:00
|
|
|
) -> None:
|
2024-03-29 14:14:03 +00:00
|
|
|
"""
|
2024-03-31 17:31:10 +01:00
|
|
|
Initialise a folder observation instance.
|
2024-03-08 14:08:35 +00:00
|
|
|
|
|
|
|
|
:param where: Where in the simulation state dictionary to find the relevant information for this folder.
|
2024-03-29 14:14:03 +00:00
|
|
|
A typical location for a folder might be ['network', 'nodes', <node_hostname>, 'folders', <folder_name>].
|
|
|
|
|
:type where: WhereType
|
|
|
|
|
:param files: List of file observation instances within the folder.
|
|
|
|
|
:type files: Iterable[FileObservation]
|
|
|
|
|
:param num_files: Number of files expected in the folder.
|
|
|
|
|
:type num_files: int
|
|
|
|
|
:param include_num_access: Whether to include the number of accesses to files in the observation.
|
|
|
|
|
:type include_num_access: bool
|
|
|
|
|
"""
|
|
|
|
|
self.where: WhereType = where
|
2024-03-08 14:08:35 +00:00
|
|
|
|
|
|
|
|
self.files: List[FileObservation] = files
|
2024-03-29 14:14:03 +00:00
|
|
|
while len(self.files) < num_files:
|
|
|
|
|
self.files.append(FileObservation(where=None, include_num_access=include_num_access))
|
|
|
|
|
while len(self.files) > num_files:
|
2024-03-08 14:08:35 +00:00
|
|
|
truncated_file = self.files.pop()
|
|
|
|
|
msg = f"Too many files in folder observation. Truncating file {truncated_file}"
|
|
|
|
|
_LOGGER.warning(msg)
|
|
|
|
|
|
|
|
|
|
self.default_observation = {
|
|
|
|
|
"health_status": 0,
|
|
|
|
|
"FILES": {i + 1: f.default_observation for i, f in enumerate(self.files)},
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-29 14:14:03 +00:00
|
|
|
def observe(self, state: Dict) -> ObsType:
|
|
|
|
|
"""
|
|
|
|
|
Generate observation based on the current state of the simulation.
|
2024-03-08 14:08:35 +00:00
|
|
|
|
2024-03-29 14:14:03 +00:00
|
|
|
:param state: Simulation state dictionary.
|
2024-03-08 14:08:35 +00:00
|
|
|
:type state: Dict
|
2024-03-29 14:14:03 +00:00
|
|
|
:return: Observation containing the health status of the folder and status of files within the folder.
|
|
|
|
|
:rtype: ObsType
|
2024-03-08 14:08:35 +00:00
|
|
|
"""
|
|
|
|
|
folder_state = access_from_nested_dict(state, self.where)
|
|
|
|
|
if folder_state is NOT_PRESENT_IN_STATE:
|
|
|
|
|
return self.default_observation
|
|
|
|
|
|
|
|
|
|
health_status = folder_state["health_status"]
|
|
|
|
|
|
|
|
|
|
obs = {}
|
|
|
|
|
|
|
|
|
|
obs["health_status"] = health_status
|
|
|
|
|
obs["FILES"] = {i + 1: file.observe(state) for i, file in enumerate(self.files)}
|
|
|
|
|
|
|
|
|
|
return obs
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def space(self) -> spaces.Space:
|
2024-03-29 14:14:03 +00:00
|
|
|
"""
|
|
|
|
|
Gymnasium space object describing the observation space shape.
|
2024-03-08 14:08:35 +00:00
|
|
|
|
2024-03-29 14:14:03 +00:00
|
|
|
:return: Gymnasium space representing the observation space for folder status.
|
2024-03-08 14:08:35 +00:00
|
|
|
:rtype: spaces.Space
|
|
|
|
|
"""
|
|
|
|
|
return spaces.Dict(
|
|
|
|
|
{
|
|
|
|
|
"health_status": spaces.Discrete(6),
|
|
|
|
|
"FILES": spaces.Dict({i + 1: f.space for i, f in enumerate(self.files)}),
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2024-03-29 14:14:03 +00:00
|
|
|
def from_config(cls, config: ConfigSchema, parent_where: WhereType = []) -> FolderObservation:
|
|
|
|
|
"""
|
|
|
|
|
Create a folder observation from a configuration schema.
|
|
|
|
|
|
|
|
|
|
:param config: Configuration schema containing the necessary information for the folder observation.
|
|
|
|
|
:type config: ConfigSchema
|
2024-03-08 14:08:35 +00:00
|
|
|
:param parent_where: Where in the simulation state dictionary to find the information about this folder's
|
2024-03-29 14:14:03 +00:00
|
|
|
parent node. A typical location for a node might be ['network', 'nodes', <node_hostname>].
|
|
|
|
|
:type parent_where: WhereType, optional
|
|
|
|
|
:return: Constructed folder observation instance.
|
2024-03-08 14:08:35 +00:00
|
|
|
:rtype: FolderObservation
|
|
|
|
|
"""
|
2024-03-29 14:14:03 +00:00
|
|
|
where = parent_where + ["folders", config.folder_name]
|
2024-03-08 14:08:35 +00:00
|
|
|
|
2024-03-29 14:14:03 +00:00
|
|
|
# pass down shared/common config items
|
|
|
|
|
for file_config in config.files:
|
|
|
|
|
file_config.include_num_access = config.include_num_access
|
2024-03-08 14:08:35 +00:00
|
|
|
|
2024-03-29 14:14:03 +00:00
|
|
|
files = [FileObservation.from_config(config=f, parent_where=where) for f in config.files]
|
|
|
|
|
return cls(where=where, files=files, num_files=config.num_files, include_num_access=config.include_num_access)
|