Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/133916/?format=api
http://patches.dpdk.org/api/patches/133916/?format=api", "web_url": "http://patches.dpdk.org/project/dpdk/patch/20231106171601.160749-16-juraj.linkes@pantheon.tech/", "project": { "id": 1, "url": "http://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": "<20231106171601.160749-16-juraj.linkes@pantheon.tech>", "list_archive_url": "https://inbox.dpdk.org/dev/20231106171601.160749-16-juraj.linkes@pantheon.tech", "date": "2023-11-06T17:15:53", "name": "[v5,15/23] dts: os session docstring update", "commit_ref": null, "pull_url": null, "state": "superseded", "archived": true, "hash": "26c8f60ec4591abe3aaf6db204796a20d6e04723", "submitter": { "id": 1626, "url": "http://patches.dpdk.org/api/people/1626/?format=api", "name": "Juraj Linkeš", "email": "juraj.linkes@pantheon.tech" }, "delegate": { "id": 2642, "url": "http://patches.dpdk.org/api/users/2642/?format=api", "username": "mcoquelin", "first_name": "Maxime", "last_name": "Coquelin", "email": "maxime.coquelin@redhat.com" }, "mbox": "http://patches.dpdk.org/project/dpdk/patch/20231106171601.160749-16-juraj.linkes@pantheon.tech/mbox/", "series": [ { "id": 30173, "url": "http://patches.dpdk.org/api/series/30173/?format=api", "web_url": "http://patches.dpdk.org/project/dpdk/list/?series=30173", "date": "2023-11-06T17:15:38", "name": "dts: add dts api docs", "version": 5, "mbox": "http://patches.dpdk.org/series/30173/mbox/" } ], "comments": "http://patches.dpdk.org/api/patches/133916/comments/", "check": "success", "checks": "http://patches.dpdk.org/api/patches/133916/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 5E736432BB;\n\tMon, 6 Nov 2023 18:18:15 +0100 (CET)", "from mails.dpdk.org (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id C71BD42D68;\n\tMon, 6 Nov 2023 18:16:28 +0100 (CET)", "from mail-ej1-f54.google.com (mail-ej1-f54.google.com\n [209.85.218.54]) by mails.dpdk.org (Postfix) with ESMTP id BACC24113D\n for <dev@dpdk.org>; Mon, 6 Nov 2023 18:16:25 +0100 (CET)", "by mail-ej1-f54.google.com with SMTP id\n a640c23a62f3a-9d2d8343dc4so699385366b.0\n for <dev@dpdk.org>; Mon, 06 Nov 2023 09:16:25 -0800 (PST)", "from jlinkes-PT-Latitude-5530.. (ip-46.34.243.197.o2inet.sk.\n [46.34.243.197]) by smtp.gmail.com with ESMTPSA id\n s10-20020a170906354a00b009b947aacb4bsm47016eja.191.2023.11.06.09.16.24\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Mon, 06 Nov 2023 09:16:25 -0800 (PST)" ], "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=pantheon.tech; s=google; t=1699290985; x=1699895785; 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=ITkrAxO5KuM/Qh2lLzYH/N5yf14RBiBwIGekQSrs0vE=;\n b=VerwjzK7MwYyETG9uNdJvJXeSigN9KuCdCLB0nKyR/UZTvuQnZ3kZ4cZNe9ra+FbmP\n q1ThRMKm2yU+sPaLSRkhdvP9+Lx89ObCxdAjBvbydEen0F3oRFlG8z8IHufUL6uD2Z+g\n wB4jAsdigWLuf6lpL5ufBLC71aqB+t2FIFMqi0kU61ohxhkeGvjFBnPzYHybuXXIsOow\n 6kXmbhiPQsiy4aittauC6DisDnhP8JTbi5TEORzOiWMWf6zTnoYDaGgUw/3+obg7WM9N\n SSJ1Cl1CBsaU/M3ygjqcVa4udOc8j1mFAB2STL9xpcQgFzStMIxX/WLVECGp2Ln0gtBe\n aWgw==", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20230601; t=1699290985; x=1699895785;\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=ITkrAxO5KuM/Qh2lLzYH/N5yf14RBiBwIGekQSrs0vE=;\n b=XK00cHtjNc1V5apuLzMObZw4St4+PtbNSOOBfBIE0BrgOrLYbR63ysNW8JJGTh+nlA\n 2UjAxVVX2KHRFKRWGcMQSiDR7KdXb9pNdE8jB6kjLVfysBl4QMc2n4uLL6opsFUyX4+G\n MbZpvIhruqqLC6tUZrJS0jW9WBSy0mL87lfpmZc/X68mF2O3KY0LBJELbXNQmC1BFSGC\n q7guobXXGxb839FidU7S9tI31TmkUIUri0ouEYTfwfZELhzcnRyavqyNxt+wCqm2Uqy8\n 2b2HXH+L4ns3mgP9NOvHQ1cbgK2sg8MltXvnVXd+5FECr+8rRx8aJ4Y6bkhkHs0gr4/k\n lgPQ==", "X-Gm-Message-State": "AOJu0YxSM5gXvckQXKcT5Usmz1rO8l+DW8Z4/+mZfFHDWf81LM88fUPd\n MU8XKUHDnowMq1NqL+ePeG3nkQ==", "X-Google-Smtp-Source": "\n AGHT+IFqgtOhyeUQWnxydNdr9viHvFS1juz0foLhJPMjlxBRaLFc9jjjbyYu3xR87a2swfNitw5/pg==", "X-Received": "by 2002:a17:907:7d93:b0:9d3:e48f:30c5 with SMTP id\n oz19-20020a1709077d9300b009d3e48f30c5mr14366710ejc.55.1699290985349;\n Mon, 06 Nov 2023 09:16:25 -0800 (PST)", "From": "=?utf-8?q?Juraj_Linke=C5=A1?= <juraj.linkes@pantheon.tech>", "To": "thomas@monjalon.net, Honnappa.Nagarahalli@arm.com,\n bruce.richardson@intel.com, jspewock@iol.unh.edu, probb@iol.unh.edu,\n paul.szczepanek@arm.com, yoan.picchi@foss.arm.com", "Cc": "dev@dpdk.org, =?utf-8?q?Juraj_Linke=C5=A1?= <juraj.linkes@pantheon.tech>", "Subject": "[PATCH v5 15/23] dts: os session docstring update", "Date": "Mon, 6 Nov 2023 18:15:53 +0100", "Message-Id": "<20231106171601.160749-16-juraj.linkes@pantheon.tech>", "X-Mailer": "git-send-email 2.34.1", "In-Reply-To": "<20231106171601.160749-1-juraj.linkes@pantheon.tech>", "References": "<20230831100407.59865-1-juraj.linkes@pantheon.tech>\n <20231106171601.160749-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 | 275 ++++++++++++++++------\n 1 file changed, 208 insertions(+), 67 deletions(-)", "diff": "diff --git a/dts/framework/testbed_model/os_session.py b/dts/framework/testbed_model/os_session.py\nindex 76e595a518..bad75d52e7 100644\n--- a/dts/framework/testbed_model/os_session.py\n+++ b/dts/framework/testbed_model/os_session.py\n@@ -2,6 +2,29 @@\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\n+ a remote :class:`~framework.testbed_model.sut_node.SutNode`.\n+ The :class:`~framework.testbed_model.sut_node.SutNode` object isn't aware what OS the node\n+ is running - it delegates the OS translation logic\n+ to :attr:`~framework.testbed_model.node.Node.main_session`. The SUT node calls\n+ :meth:`~OSSession.remove_remote_dir` with a generic, OS-unaware path and\n+ the :attr:`~framework.testbed_model.node.Node.main_session` translates that\n+ to ``rm -rf`` if the node's OS is Linux and other commands for other OSs.\n+ 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 +51,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 +75,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 +91,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 +110,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 to execute the command.\n+ privileged: Whether to run the command with administrative privileges.\n+ verify: If 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 True and the command failed.\n \"\"\"\n if privileged:\n command = self._get_privileged_command(command)\n@@ -89,8 +140,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 +177,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 +221,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 +237,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 +254,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 +268,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 +286,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 then 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 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 to execute.\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:`~framework.testbed_model.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 socket.\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 +408,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": [ "v5", "15/23" ] }{ "id": 133916, "url": "