On Wed, Nov 22, 2023 at 1:18 PM Yoan Picchi <yoan.pic...@foss.arm.com> wrote: > > On 11/15/23 13:09, Juraj Linkeš wrote: > > Format according to the Google format and PEP257, with slight > > deviations. > > > > Signed-off-by: Juraj Linkeš <juraj.lin...@pantheon.tech> > > --- > > dts/framework/testbed_model/node.py | 191 +++++++++++++++++++--------- > > 1 file changed, 131 insertions(+), 60 deletions(-) > > > > diff --git a/dts/framework/testbed_model/node.py > > b/dts/framework/testbed_model/node.py > > index fa5b143cdd..f93b4acecd 100644 > > --- a/dts/framework/testbed_model/node.py > > +++ b/dts/framework/testbed_model/node.py > > @@ -3,8 +3,13 @@ > > # Copyright(c) 2022-2023 PANTHEON.tech s.r.o. > > # Copyright(c) 2022-2023 University of New Hampshire > > > > -""" > > -A node is a generic host that DTS connects to and manages. > > +"""Common functionality for node management. > > + > > +A node is any host/server DTS connects to. > > + > > +The base class, :class:`Node`, provides functionality common to all nodes > > and is supposed > > +to be extended by subclasses with functionality specific to each node type. > > functionality -> functionalities >
Ack. > > +The decorator :func:`Node.skip_setup` can be used without subclassing. > > """ > > > > from abc import ABC > > @@ -35,10 +40,22 @@ > > > > > > class Node(ABC): > > - """ > > - Basic class for node management. This class implements methods that > > - manage a node, such as information gathering (of CPU/PCI/NIC) and > > - environment setup. > > + """The base class for node management. > > + > > + It shouldn't be instantiated, but rather subclassed. > > + It implements common methods to manage any node: > > + > > + * Connection to the node, > > + * Hugepages setup. > > + > > + Attributes: > > + main_session: The primary OS-aware remote session used to > > communicate with the node. > > + config: The node configuration. > > + name: The name of the node. > > + lcores: The list of logical cores that DTS can use on the node. > > + It's derived from logical cores present on the node and the > > test run configuration. > > + ports: The ports of this node specified in the test run > > configuration. > > + virtual_devices: The virtual devices used on the node. > > """ > > > > main_session: OSSession > > @@ -52,6 +69,17 @@ class Node(ABC): > > virtual_devices: list[VirtualDevice] > > > > def __init__(self, node_config: NodeConfiguration): > > + """Connect to the node and gather info during initialization. > > + > > + Extra gathered information: > > + > > + * The list of available logical CPUs. This is then filtered by > > + the ``lcores`` configuration in the YAML test run configuration > > file, > > + * Information about ports from the YAML test run configuration > > file. > > + > > + Args: > > + node_config: The node's test run configuration. > > + """ > > self.config = node_config > > self.name = node_config.name > > self._logger = getLogger(self.name) > > @@ -60,7 +88,7 @@ def __init__(self, node_config: NodeConfiguration): > > self._logger.info(f"Connected to node: {self.name}") > > > > self._get_remote_cpus() > > - # filter the node lcores according to user config > > + # filter the node lcores according to the test run configuration > > self.lcores = LogicalCoreListFilter( > > self.lcores, LogicalCoreList(self.config.lcores) > > ).filter() > > @@ -76,9 +104,14 @@ def _init_ports(self) -> None: > > self.configure_port_state(port) > > > > def set_up_execution(self, execution_config: ExecutionConfiguration) > > -> None: > > - """ > > - Perform the execution setup that will be done for each execution > > - this node is part of. > > + """Execution setup steps. > > + > > + Configure hugepages and call :meth:`_set_up_execution` where > > + the rest of the configuration steps (if any) are implemented. > > + > > + Args: > > + execution_config: The execution test run configuration > > according to which > > + the setup steps will be taken. > > """ > > self._setup_hugepages() > > self._set_up_execution(execution_config) > > @@ -87,58 +120,74 @@ def set_up_execution(self, execution_config: > > ExecutionConfiguration) -> None: > > self.virtual_devices.append(VirtualDevice(vdev)) > > > > def _set_up_execution(self, execution_config: ExecutionConfiguration) > > -> None: > > - """ > > - This method exists to be optionally overwritten by derived classes > > and > > - is not decorated so that the derived class doesn't have to use the > > decorator. > > + """Optional additional execution setup steps for subclasses. > > + > > + Subclasses should override this if they need to add additional > > execution setup steps. > > """ > > > > def tear_down_execution(self) -> None: > > - """ > > - Perform the execution teardown that will be done after each > > execution > > - this node is part of concludes. > > + """Execution teardown steps. > > + > > + There are currently no common execution teardown steps common to > > all DTS node types. > > """ > > self.virtual_devices = [] > > self._tear_down_execution() > > > > def _tear_down_execution(self) -> None: > > - """ > > - This method exists to be optionally overwritten by derived classes > > and > > - is not decorated so that the derived class doesn't have to use the > > decorator. > > + """Optional additional execution teardown steps for subclasses. > > + > > + Subclasses should override this if they need to add additional > > execution teardown steps. > > """ > > > > def set_up_build_target( > > self, build_target_config: BuildTargetConfiguration > > ) -> None: > > - """ > > - Perform the build target setup that will be done for each build > > target > > - tested on this node. > > + """Build target setup steps. > > + > > + There are currently no common build target setup steps common to > > all DTS node types. > > + > > + Args: > > + build_target_config: The build target test run configuration > > according to which > > + the setup steps will be taken. > > """ > > self._set_up_build_target(build_target_config) > > > > def _set_up_build_target( > > self, build_target_config: BuildTargetConfiguration > > ) -> None: > > - """ > > - This method exists to be optionally overwritten by derived classes > > and > > - is not decorated so that the derived class doesn't have to use the > > decorator. > > + """Optional additional build target setup steps for subclasses. > > + > > + Subclasses should override this if they need to add additional > > build target setup steps. > > """ > > > > def tear_down_build_target(self) -> None: > > - """ > > - Perform the build target teardown that will be done after each > > build target > > - tested on this node. > > + """Build target teardown steps. > > + > > + There are currently no common build target teardown steps common > > to all DTS node types. > > """ > > self._tear_down_build_target() > > > > def _tear_down_build_target(self) -> None: > > - """ > > - This method exists to be optionally overwritten by derived classes > > and > > - is not decorated so that the derived class doesn't have to use the > > decorator. > > + """Optional additional build target teardown steps for subclasses. > > + > > + Subclasses should override this if they need to add additional > > build target teardown steps. > > """ > > > > def create_session(self, name: str) -> OSSession: > > - """ > > - Create and return a new OSSession tailored to the remote OS. > > + """Create and return a new OS-aware remote session. > > + > > + The returned session won't be used by the node creating it. The > > session must be used by > > + the caller. The session will be maintained for the entire > > lifecycle of the node object, > > + at the end of which the session will be cleaned up automatically. > > + > > + Note: > > + Any number of these supplementary sessions may be created. > > + > > + Args: > > + name: The name of the session. > > + > > + Returns: > > + A new OS-aware remote session. > > """ > > session_name = f"{self.name} {name}" > > connection = create_session( > > @@ -156,19 +205,19 @@ def create_interactive_shell( > > privileged: bool = False, > > app_args: str = "", > > ) -> InteractiveShellType: > > - """Create a handler for an interactive session. > > + """Factory for interactive session handlers. > > > > - Instantiate shell_cls according to the remote OS specifics. > > + Instantiate `shell_cls` according to the remote OS specifics. > > > > Args: > > shell_cls: The class of the shell. > > - timeout: Timeout for reading output from the SSH channel. If > > you are > > - reading from the buffer and don't receive any data within > > the timeout > > - it will throw an error. > > + timeout: Timeout for reading output from the SSH channel. If > > you are reading from > > + the buffer and don't receive any data within the timeout > > it will throw an error. > > privileged: Whether to run the shell with administrative > > privileges. > > app_args: The arguments to be passed to the application. > > + > > Returns: > > - Instance of the desired interactive application. > > + An instance of the desired interactive application shell. > > """ > > if not shell_cls.dpdk_app: > > shell_cls.path = > > self.main_session.join_remote_path(shell_cls.path) > > @@ -185,14 +234,22 @@ def filter_lcores( > > filter_specifier: LogicalCoreCount | LogicalCoreList, > > ascending: bool = True, > > ) -> list[LogicalCore]: > > - """ > > - Filter the LogicalCores found on the Node according to > > - a LogicalCoreCount or a LogicalCoreList. > > + """Filter the node's logical cores that DTS can use. > > + > > + Logical cores that DTS can use are the ones that are present on > > the node, but filtered > > + according to the test run configuration. The `filter_specifier` > > will filter cores from > > + those logical cores. > > + > > + Args: > > + filter_specifier: Two different filters can be used, one that > > specifies the number > > + of logical cores per core, cores per socket and the number > > of sockets, > > + and another one that specifies a logical core list. > > + ascending: If :data:`True`, use cores with the lowest > > numerical id first and continue > > + in ascending order. If :data:`False`, start with the > > highest id and continue > > + in descending order. This ordering affects which sockets > > to consider first as well. > > > > - If ascending is True, use cores with the lowest numerical id first > > - and continue in ascending order. If False, start with the highest > > - id and continue in descending order. This ordering affects which > > - sockets to consider first as well. > > + Returns: > > + The filtered logical cores. > > """ > > self._logger.debug(f"Filtering {filter_specifier} from > > {self.lcores}.") > > return lcore_filter( > > @@ -202,17 +259,14 @@ def filter_lcores( > > ).filter() > > > > def _get_remote_cpus(self) -> None: > > - """ > > - Scan CPUs in the remote OS and store a list of LogicalCores. > > - """ > > + """Scan CPUs in the remote OS and store a list of LogicalCores.""" > > self._logger.info("Getting CPU information.") > > self.lcores = > > self.main_session.get_remote_cpus(self.config.use_first_core) > > > > def _setup_hugepages(self) -> None: > > - """ > > - Setup hugepages on the Node. Different architectures can supply > > different > > - amounts of memory for hugepages and numa-based hugepage allocation > > may need > > - to be considered. > > + """Setup hugepages on the node. > > + > > + Configure the hugepages only if they're specified in the node's > > test run configuration. > > """ > > if self.config.hugepages: > > self.main_session.setup_hugepages( > > @@ -220,8 +274,11 @@ def _setup_hugepages(self) -> None: > > ) > > > > def configure_port_state(self, port: Port, enable: bool = True) -> > > None: > > - """ > > - Enable/disable port. > > + """Enable/disable `port`. > > + > > + Args: > > + port: The port to enable/disable. > > + enable: :data:`True` to enable, :data:`False` to disable. > > """ > > self.main_session.configure_port_state(port, enable) > > > > @@ -231,15 +288,17 @@ def configure_port_ip_address( > > port: Port, > > delete: bool = False, > > ) -> None: > > - """ > > - Configure the IP address of a port on this node. > > + """Add an IP address to `port` on this node. > > + > > + Args: > > + address: The IP address with mask in CIDR format. Can be > > either IPv4 or IPv6. > > + port: The port to which to add the address. > > + delete: If :data:`True`, will delete the address from the port > > instead of adding it. > > """ > > self.main_session.configure_port_ip_address(address, port, delete) > > > > def close(self) -> None: > > - """ > > - Close all connections and free other resources. > > - """ > > + """Close all connections and free other resources.""" > > if self.main_session: > > self.main_session.close() > > for session in self._other_sessions: > > @@ -248,6 +307,11 @@ def close(self) -> None: > > > > @staticmethod > > def skip_setup(func: Callable[..., Any]) -> Callable[..., Any]: > > + """Skip the decorated function. > > + > > + The :option:`--skip-setup` command line argument and the > > :envvar:`DTS_SKIP_SETUP` > > + environment variable enable the decorator. > > + """ > > if SETTINGS.skip_setup: > > return lambda *args: None > > else: > > @@ -257,6 +321,13 @@ def skip_setup(func: Callable[..., Any]) -> > > Callable[..., Any]: > > def create_session( > > node_config: NodeConfiguration, name: str, logger: DTSLOG > > ) -> OSSession: > > + """Factory for OS-aware sessions. > > + > > + Args: > > + node_config: The test run configuration of the node to connect to. > > + name: The name of the session. > > + logger: The logger instance this session will use. > > + """ > > match node_config.os: > > case OS.linux: > > return LinuxSession(node_config, name, logger) >