Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/134803/?format=api
https://patches.dpdk.org/api/patches/134803/?format=api", "web_url": "https://patches.dpdk.org/project/dpdk/patch/20231204102429.106709-16-juraj.linkes@pantheon.tech/", "project": { "id": 1, "url": "https://patches.dpdk.org/api/projects/1/?format=api", "name": "DPDK", "link_name": "dpdk", "list_id": "dev.dpdk.org", "list_email": "dev@dpdk.org", "web_url": "http://core.dpdk.org", "scm_url": "git://dpdk.org/dpdk", "webscm_url": "http://git.dpdk.org/dpdk", "list_archive_url": "https://inbox.dpdk.org/dev", "list_archive_url_format": "https://inbox.dpdk.org/dev/{}", "commit_url_format": "" }, "msgid": "<20231204102429.106709-16-juraj.linkes@pantheon.tech>", "list_archive_url": "https://inbox.dpdk.org/dev/20231204102429.106709-16-juraj.linkes@pantheon.tech", "date": "2023-12-04T10:24:23", "name": "[v9,15/21] dts: os session docstring update", "commit_ref": null, "pull_url": null, "state": "accepted", "archived": true, "hash": "6e3fa7727ca0057ac22477471680ab290b818614", "submitter": { "id": 1626, "url": "https://patches.dpdk.org/api/people/1626/?format=api", "name": "Juraj Linkeš", "email": "juraj.linkes@pantheon.tech" }, "delegate": { "id": 1, "url": "https://patches.dpdk.org/api/users/1/?format=api", "username": "tmonjalo", "first_name": "Thomas", "last_name": "Monjalon", "email": "thomas@monjalon.net" }, "mbox": "https://patches.dpdk.org/project/dpdk/patch/20231204102429.106709-16-juraj.linkes@pantheon.tech/mbox/", "series": [ { "id": 30441, "url": "https://patches.dpdk.org/api/series/30441/?format=api", "web_url": "https://patches.dpdk.org/project/dpdk/list/?series=30441", "date": "2023-12-04T10:24:08", "name": "dts: docstrings update", "version": 9, "mbox": "https://patches.dpdk.org/series/30441/mbox/" } ], "comments": "https://patches.dpdk.org/api/patches/134803/comments/", "check": "success", "checks": "https://patches.dpdk.org/api/patches/134803/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<dev-bounces@dpdk.org>", "X-Original-To": "patchwork@inbox.dpdk.org", "Delivered-To": "patchwork@inbox.dpdk.org", "Received": [ "from mails.dpdk.org (mails.dpdk.org [217.70.189.124])\n\tby inbox.dpdk.org (Postfix) with ESMTP id 081034366A;\n\tMon, 4 Dec 2023 11:26:27 +0100 (CET)", "from mails.dpdk.org (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id 2D95C42D2A;\n\tMon, 4 Dec 2023 11:24:53 +0100 (CET)", "from mail-wm1-f50.google.com (mail-wm1-f50.google.com\n [209.85.128.50]) by mails.dpdk.org (Postfix) with ESMTP id 8EA3B42670\n for <dev@dpdk.org>; Mon, 4 Dec 2023 11:24:46 +0100 (CET)", "by mail-wm1-f50.google.com with SMTP id\n 5b1f17b1804b1-40bda47b7c1so22906025e9.1\n for <dev@dpdk.org>; Mon, 04 Dec 2023 02:24:46 -0800 (PST)", "from jlinkes-PT-Latitude-5530.pantheon.local ([81.89.53.154])\n by smtp.gmail.com with ESMTPSA id\n m28-20020a05600c3b1c00b0040b2b38a1fasm14255415wms.4.2023.12.04.02.24.45\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Mon, 04 Dec 2023 02:24:45 -0800 (PST)" ], "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=pantheon.tech; s=google; t=1701685486; x=1702290286; darn=dpdk.org;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=e11UDpSi+pdOs0mbM57SdnfS7daFl2y2OQBCS57ioc4=;\n b=LZBuE12ZG/JjvB9gWKqPlxuVEiXR5iL9W53YeRS8tE3aQlKk68GJPGggyUJerU8/BP\n UZWM+mLvJjk1MNG+tr8PxP4g63cACVxoPssROT/8umeaAj/i0nXRrhmlJUUUwVnsVEGa\n RMkkqCJ8SrVulnW4lZWm647TT8k7BqzHwKei0ukavQ4Nct4JfQzhiJ0T3VE9TVD6EdQp\n Mt5aAyl2jbDbDp+3AqHKKqJQ+mcDlvTRQRqspEFI31KayymcaJbt4Awi204i0Dk5wgpu\n 9BZZ7llLKxfLNBEq0d6LaakFMHV7QEL1K+L1VFRdaY2rrvZWtwTA1cw/JdKMUixvJ6Mt\n yDaA==", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20230601; t=1701685486; x=1702290286;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n :subject:date:message-id:reply-to;\n bh=e11UDpSi+pdOs0mbM57SdnfS7daFl2y2OQBCS57ioc4=;\n b=GMfjSBaOkseHoinHMNUWg0814h4P3kpB3k456zF4YsGYqVKpdMGwOcbx/opSltAUlF\n 8ZtgN+xuypszIDDNd6WT5xpidfJMZvdapv/XcAOmeTn9EWwAVVaQO/HvUR75K5ET6Sn7\n 5xx9iRav8R+7zV9lQJuTh+0pj52Hke2mprsTtIcSDt9JyGXamBEQQ3mubwjla84PlkwB\n 9CIhSMpU5u0EeUYQW6aIYw8L0NPkG1SX1P+Ab87YT+rGXG/0n73LruRM/6Prt2Wpvm/d\n 26SwXZVdwuKoWa3mD4pWYJop1ZYADfqzWaSMJvi0VbihdH9ZqL6I0ryXwfM8CDl+RrKH\n 1Yyg==", "X-Gm-Message-State": "AOJu0Yzsb+2BXyFFar2dz1hEls+l4FdYANwDxysivoSaiYUj1+kOTbpK\n I6s00ToGZtUeOWMueB3KN1+Krw==", "X-Google-Smtp-Source": "\n AGHT+IE0JWlDlGLuq7Fj5S7EkP5rK2t0HIm6jqdJwS59y5bbXcjV0zlAqkBvm/mSRKlNEyk83E3kAw==", "X-Received": "by 2002:a05:600c:3c90:b0:40b:5f03:b3ca with SMTP id\n bg16-20020a05600c3c9000b0040b5f03b3camr1051555wmb.236.1701685486141;\n Mon, 04 Dec 2023 02:24:46 -0800 (PST)", "From": "=?utf-8?q?Juraj_Linke=C5=A1?= <juraj.linkes@pantheon.tech>", "To": "thomas@monjalon.net, Honnappa.Nagarahalli@arm.com, jspewock@iol.unh.edu,\n probb@iol.unh.edu, paul.szczepanek@arm.com, yoan.picchi@foss.arm.com,\n Luca.Vizzarro@arm.com", "Cc": "dev@dpdk.org, =?utf-8?q?Juraj_Linke=C5=A1?= <juraj.linkes@pantheon.tech>", "Subject": "[PATCH v9 15/21] dts: os session docstring update", "Date": "Mon, 4 Dec 2023 11:24:23 +0100", "Message-Id": "<20231204102429.106709-16-juraj.linkes@pantheon.tech>", "X-Mailer": "git-send-email 2.34.1", "In-Reply-To": "<20231204102429.106709-1-juraj.linkes@pantheon.tech>", "References": "<20231123151344.162812-1-juraj.linkes@pantheon.tech>\n <20231204102429.106709-1-juraj.linkes@pantheon.tech>", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=UTF-8", "Content-Transfer-Encoding": "8bit", "X-BeenThere": "dev@dpdk.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "DPDK patches and discussions <dev.dpdk.org>", "List-Unsubscribe": "<https://mails.dpdk.org/options/dev>,\n <mailto:dev-request@dpdk.org?subject=unsubscribe>", "List-Archive": "<http://mails.dpdk.org/archives/dev/>", "List-Post": "<mailto:dev@dpdk.org>", "List-Help": "<mailto:dev-request@dpdk.org?subject=help>", "List-Subscribe": "<https://mails.dpdk.org/listinfo/dev>,\n <mailto:dev-request@dpdk.org?subject=subscribe>", "Errors-To": "dev-bounces@dpdk.org" }, "content": "Format according to the Google format and PEP257, with slight\ndeviations.\n\nSigned-off-by: Juraj Linkeš <juraj.linkes@pantheon.tech>\n---\n dts/framework/testbed_model/os_session.py | 272 ++++++++++++++++------\n 1 file changed, 205 insertions(+), 67 deletions(-)", "diff": "diff --git a/dts/framework/testbed_model/os_session.py b/dts/framework/testbed_model/os_session.py\nindex 76e595a518..ac6bb5e112 100644\n--- a/dts/framework/testbed_model/os_session.py\n+++ b/dts/framework/testbed_model/os_session.py\n@@ -2,6 +2,26 @@\n # Copyright(c) 2023 PANTHEON.tech s.r.o.\n # Copyright(c) 2023 University of New Hampshire\n \n+\"\"\"OS-aware remote session.\n+\n+DPDK supports multiple different operating systems, meaning it can run on these different operating\n+systems. This module defines the common API that OS-unaware layers use and translates the API into\n+OS-aware calls/utility usage.\n+\n+Note:\n+ Running commands with administrative privileges requires OS awareness. This is the only layer\n+ that's aware of OS differences, so this is where non-privileged command get converted\n+ to privileged commands.\n+\n+Example:\n+ A user wishes to remove a directory on a remote :class:`~.sut_node.SutNode`.\n+ The :class:`~.sut_node.SutNode` object isn't aware what OS the node is running - it delegates\n+ the OS translation logic to :attr:`~.node.Node.main_session`. The SUT node calls\n+ :meth:`~OSSession.remove_remote_dir` with a generic, OS-unaware path and\n+ the :attr:`~.node.Node.main_session` translates that to ``rm -rf`` if the node's OS is Linux\n+ and other commands for other OSs. It also translates the path to match the underlying OS.\n+\"\"\"\n+\n from abc import ABC, abstractmethod\n from collections.abc import Iterable\n from ipaddress import IPv4Interface, IPv6Interface\n@@ -28,10 +48,16 @@\n \n \n class OSSession(ABC):\n- \"\"\"\n- The OS classes create a DTS node remote session and implement OS specific\n+ \"\"\"OS-unaware to OS-aware translation API definition.\n+\n+ The OSSession classes create a remote session to a DTS node and implement OS specific\n behavior. There a few control methods implemented by the base class, the rest need\n- to be implemented by derived classes.\n+ to be implemented by subclasses.\n+\n+ Attributes:\n+ name: The name of the session.\n+ remote_session: The remote session maintaining the connection to the node.\n+ interactive_session: The interactive remote session maintaining the connection to the node.\n \"\"\"\n \n _config: NodeConfiguration\n@@ -46,6 +72,15 @@ def __init__(\n name: str,\n logger: DTSLOG,\n ):\n+ \"\"\"Initialize the OS-aware session.\n+\n+ Connect to the node right away and also create an interactive remote session.\n+\n+ Args:\n+ node_config: The test run configuration of the node to connect to.\n+ name: The name of the session.\n+ logger: The logger instance this session will use.\n+ \"\"\"\n self._config = node_config\n self.name = name\n self._logger = logger\n@@ -53,15 +88,15 @@ def __init__(\n self.interactive_session = create_interactive_session(node_config, logger)\n \n def close(self, force: bool = False) -> None:\n- \"\"\"\n- Close the remote session.\n+ \"\"\"Close the underlying remote session.\n+\n+ Args:\n+ force: Force the closure of the connection.\n \"\"\"\n self.remote_session.close(force)\n \n def is_alive(self) -> bool:\n- \"\"\"\n- Check whether the remote session is still responding.\n- \"\"\"\n+ \"\"\"Check whether the underlying remote session is still responding.\"\"\"\n return self.remote_session.is_alive()\n \n def send_command(\n@@ -72,10 +107,23 @@ def send_command(\n verify: bool = False,\n env: dict | None = None,\n ) -> CommandResult:\n- \"\"\"\n- An all-purpose API in case the command to be executed is already\n- OS-agnostic, such as when the path to the executed command has been\n- constructed beforehand.\n+ \"\"\"An all-purpose API for OS-agnostic commands.\n+\n+ This can be used for an execution of a portable command that's executed the same way\n+ on all operating systems, such as Python.\n+\n+ The :option:`--timeout` command line argument and the :envvar:`DTS_TIMEOUT`\n+ environment variable configure the timeout of command execution.\n+\n+ Args:\n+ command: The command to execute.\n+ timeout: Wait at most this long in seconds for `command` execution to complete.\n+ privileged: Whether to run the command with administrative privileges.\n+ verify: If :data:`True`, will check the exit code of the command.\n+ env: A dictionary with environment variables to be used with the command execution.\n+\n+ Raises:\n+ RemoteCommandExecutionError: If verify is :data:`True` and the command failed.\n \"\"\"\n if privileged:\n command = self._get_privileged_command(command)\n@@ -89,8 +137,20 @@ def create_interactive_shell(\n privileged: bool,\n app_args: str,\n ) -> InteractiveShellType:\n- \"\"\"\n- See \"create_interactive_shell\" in SutNode\n+ \"\"\"Factory for interactive session handlers.\n+\n+ Instantiate `shell_cls` according to the remote OS specifics.\n+\n+ Args:\n+ shell_cls: The class of the shell.\n+ timeout: Timeout for reading output from the SSH channel. If you are\n+ reading from the buffer and don't receive any data within the timeout\n+ it will throw an error.\n+ privileged: Whether to run the shell with administrative privileges.\n+ app_args: The arguments to be passed to the application.\n+\n+ Returns:\n+ An instance of the desired interactive application shell.\n \"\"\"\n return shell_cls(\n self.interactive_session.session,\n@@ -114,27 +174,42 @@ def _get_privileged_command(command: str) -> str:\n \n @abstractmethod\n def guess_dpdk_remote_dir(self, remote_dir: str | PurePath) -> PurePath:\n- \"\"\"\n- Try to find DPDK remote dir in remote_dir.\n+ \"\"\"Try to find DPDK directory in `remote_dir`.\n+\n+ The directory is the one which is created after the extraction of the tarball. The files\n+ are usually extracted into a directory starting with ``dpdk-``.\n+\n+ Returns:\n+ The absolute path of the DPDK remote directory, empty path if not found.\n \"\"\"\n \n @abstractmethod\n def get_remote_tmp_dir(self) -> PurePath:\n- \"\"\"\n- Get the path of the temporary directory of the remote OS.\n+ \"\"\"Get the path of the temporary directory of the remote OS.\n+\n+ Returns:\n+ The absolute path of the temporary directory.\n \"\"\"\n \n @abstractmethod\n def get_dpdk_build_env_vars(self, arch: Architecture) -> dict:\n- \"\"\"\n- Create extra environment variables needed for the target architecture. Get\n- information from the node if needed.\n+ \"\"\"Create extra environment variables needed for the target architecture.\n+\n+ Different architectures may require different configuration, such as setting 32-bit CFLAGS.\n+\n+ Returns:\n+ A dictionary with keys as environment variables.\n \"\"\"\n \n @abstractmethod\n def join_remote_path(self, *args: str | PurePath) -> PurePath:\n- \"\"\"\n- Join path parts using the path separator that fits the remote OS.\n+ \"\"\"Join path parts using the path separator that fits the remote OS.\n+\n+ Args:\n+ args: Any number of paths to join.\n+\n+ Returns:\n+ The resulting joined path.\n \"\"\"\n \n @abstractmethod\n@@ -143,13 +218,13 @@ def copy_from(\n source_file: str | PurePath,\n destination_file: str | PurePath,\n ) -> None:\n- \"\"\"Copy a file from the remote Node to the local filesystem.\n+ \"\"\"Copy a file from the remote node to the local filesystem.\n \n- Copy source_file from the remote Node associated with this remote\n- session to destination_file on the local filesystem.\n+ Copy `source_file` from the remote node associated with this remote\n+ session to `destination_file` on the local filesystem.\n \n Args:\n- source_file: the file on the remote Node.\n+ source_file: the file on the remote node.\n destination_file: a file or directory path on the local filesystem.\n \"\"\"\n \n@@ -159,14 +234,14 @@ def copy_to(\n source_file: str | PurePath,\n destination_file: str | PurePath,\n ) -> None:\n- \"\"\"Copy a file from local filesystem to the remote Node.\n+ \"\"\"Copy a file from local filesystem to the remote node.\n \n- Copy source_file from local filesystem to destination_file\n- on the remote Node associated with this remote session.\n+ Copy `source_file` from local filesystem to `destination_file`\n+ on the remote node associated with this remote session.\n \n Args:\n source_file: the file on the local filesystem.\n- destination_file: a file or directory path on the remote Node.\n+ destination_file: a file or directory path on the remote node.\n \"\"\"\n \n @abstractmethod\n@@ -176,8 +251,12 @@ def remove_remote_dir(\n recursive: bool = True,\n force: bool = True,\n ) -> None:\n- \"\"\"\n- Remove remote directory, by default remove recursively and forcefully.\n+ \"\"\"Remove remote directory, by default remove recursively and forcefully.\n+\n+ Args:\n+ remote_dir_path: The path of the directory to remove.\n+ recursive: If :data:`True`, also remove all contents inside the directory.\n+ force: If :data:`True`, ignore all warnings and try to remove at all costs.\n \"\"\"\n \n @abstractmethod\n@@ -186,9 +265,12 @@ def extract_remote_tarball(\n remote_tarball_path: str | PurePath,\n expected_dir: str | PurePath | None = None,\n ) -> None:\n- \"\"\"\n- Extract remote tarball in place. If expected_dir is a non-empty string, check\n- whether the dir exists after extracting the archive.\n+ \"\"\"Extract remote tarball in its remote directory.\n+\n+ Args:\n+ remote_tarball_path: The path of the tarball on the remote node.\n+ expected_dir: If non-empty, check whether `expected_dir` exists after extracting\n+ the archive.\n \"\"\"\n \n @abstractmethod\n@@ -201,69 +283,119 @@ def build_dpdk(\n rebuild: bool = False,\n timeout: float = SETTINGS.compile_timeout,\n ) -> None:\n- \"\"\"\n- Build DPDK in the input dir with specified environment variables and meson\n- arguments.\n+ \"\"\"Build DPDK on the remote node.\n+\n+ An extracted DPDK tarball must be present on the node. The build consists of two steps::\n+\n+ meson setup <meson args> remote_dpdk_dir remote_dpdk_build_dir\n+ ninja -C remote_dpdk_build_dir\n+\n+ The :option:`--compile-timeout` command line argument and the :envvar:`DTS_COMPILE_TIMEOUT`\n+ environment variable configure the timeout of DPDK build.\n+\n+ Args:\n+ env_vars: Use these environment variables when building DPDK.\n+ meson_args: Use these meson arguments when building DPDK.\n+ remote_dpdk_dir: The directory on the remote node where DPDK will be built.\n+ remote_dpdk_build_dir: The target build directory on the remote node.\n+ rebuild: If :data:`True`, do a subsequent build with ``meson configure`` instead\n+ of ``meson setup``.\n+ timeout: Wait at most this long in seconds for the build execution to complete.\n \"\"\"\n \n @abstractmethod\n def get_dpdk_version(self, version_path: str | PurePath) -> str:\n- \"\"\"\n- Inspect DPDK version on the remote node from version_path.\n+ \"\"\"Inspect the DPDK version on the remote node.\n+\n+ Args:\n+ version_path: The path to the VERSION file containing the DPDK version.\n+\n+ Returns:\n+ The DPDK version.\n \"\"\"\n \n @abstractmethod\n def get_remote_cpus(self, use_first_core: bool) -> list[LogicalCore]:\n- \"\"\"\n- Compose a list of LogicalCores present on the remote node.\n- If use_first_core is False, the first physical core won't be used.\n+ r\"\"\"Get the list of :class:`~.cpu.LogicalCore`\\s on the remote node.\n+\n+ Args:\n+ use_first_core: If :data:`False`, the first physical core won't be used.\n+\n+ Returns:\n+ The logical cores present on the node.\n \"\"\"\n \n @abstractmethod\n def kill_cleanup_dpdk_apps(self, dpdk_prefix_list: Iterable[str]) -> None:\n- \"\"\"\n- Kill and cleanup all DPDK apps identified by dpdk_prefix_list. If\n- dpdk_prefix_list is empty, attempt to find running DPDK apps to kill and clean.\n+ \"\"\"Kill and cleanup all DPDK apps.\n+\n+ Args:\n+ dpdk_prefix_list: Kill all apps identified by `dpdk_prefix_list`.\n+ If `dpdk_prefix_list` is empty, attempt to find running DPDK apps to kill and clean.\n \"\"\"\n \n @abstractmethod\n def get_dpdk_file_prefix(self, dpdk_prefix: str) -> str:\n- \"\"\"\n- Get the DPDK file prefix that will be used when running DPDK apps.\n+ \"\"\"Make OS-specific modification to the DPDK file prefix.\n+\n+ Args:\n+ dpdk_prefix: The OS-unaware file prefix.\n+\n+ Returns:\n+ The OS-specific file prefix.\n \"\"\"\n \n @abstractmethod\n- def setup_hugepages(self, hugepage_amount: int, force_first_numa: bool) -> None:\n- \"\"\"\n- Get the node's Hugepage Size, configure the specified amount of hugepages\n+ def setup_hugepages(self, hugepage_count: int, force_first_numa: bool) -> None:\n+ \"\"\"Configure hugepages on the node.\n+\n+ Get the node's Hugepage Size, configure the specified count of hugepages\n if needed and mount the hugepages if needed.\n- If force_first_numa is True, configure hugepages just on the first socket.\n+\n+ Args:\n+ hugepage_count: Configure this many hugepages.\n+ force_first_numa: If :data:`True`, configure hugepages just on the first numa node.\n \"\"\"\n \n @abstractmethod\n def get_compiler_version(self, compiler_name: str) -> str:\n- \"\"\"\n- Get installed version of compiler used for DPDK\n+ \"\"\"Get installed version of compiler used for DPDK.\n+\n+ Args:\n+ compiler_name: The name of the compiler executable.\n+\n+ Returns:\n+ The compiler's version.\n \"\"\"\n \n @abstractmethod\n def get_node_info(self) -> NodeInfo:\n- \"\"\"\n- Collect information about the node\n+ \"\"\"Collect additional information about the node.\n+\n+ Returns:\n+ Node information.\n \"\"\"\n \n @abstractmethod\n def update_ports(self, ports: list[Port]) -> None:\n- \"\"\"\n- Get additional information about ports:\n- Logical name (e.g. enp7s0) if applicable\n- Mac address\n+ \"\"\"Get additional information about ports from the operating system and update them.\n+\n+ The additional information is:\n+\n+ * Logical name (e.g. ``enp7s0``) if applicable,\n+ * Mac address.\n+\n+ Args:\n+ ports: The ports to update.\n \"\"\"\n \n @abstractmethod\n def configure_port_state(self, port: Port, enable: bool) -> None:\n- \"\"\"\n- Enable/disable port.\n+ \"\"\"Enable/disable `port` in the operating system.\n+\n+ Args:\n+ port: The port to configure.\n+ enable: If :data:`True`, enable the port, otherwise shut it down.\n \"\"\"\n \n @abstractmethod\n@@ -273,12 +405,18 @@ def configure_port_ip_address(\n port: Port,\n delete: bool,\n ) -> None:\n- \"\"\"\n- Configure (add or delete) an IP address of the input port.\n+ \"\"\"Configure an IP address on `port` in the operating system.\n+\n+ Args:\n+ address: The address to configure.\n+ port: The port to configure.\n+ delete: If :data:`True`, remove the IP address, otherwise configure it.\n \"\"\"\n \n @abstractmethod\n def configure_ipv4_forwarding(self, enable: bool) -> None:\n- \"\"\"\n- Enable IPv4 forwarding in the underlying OS.\n+ \"\"\"Enable IPv4 forwarding in the operating system.\n+\n+ Args:\n+ enable: If :data:`True`, enable the forwarding, otherwise disable it.\n \"\"\"\n", "prefixes": [ "v9", "15/21" ] }{ "id": 134803, "url": "