[v7,17/21] dts: node docstring update
Checks
Commit Message
Format according to the Google format and PEP257, with slight
deviations.
Signed-off-by: Juraj Linkeš <juraj.linkes@pantheon.tech>
---
dts/framework/testbed_model/node.py | 191 +++++++++++++++++++---------
1 file changed, 131 insertions(+), 60 deletions(-)
Comments
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.linkes@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
> +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)
On Wed, Nov 22, 2023 at 1:18 PM Yoan Picchi <yoan.picchi@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.linkes@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)
>
@@ -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.
+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)