get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/134581/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 134581,
    "url": "https://patches.dpdk.org/api/patches/134581/?format=api",
    "web_url": "https://patches.dpdk.org/project/dpdk/patch/20231123151344.162812-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": "<20231123151344.162812-16-juraj.linkes@pantheon.tech>",
    "list_archive_url": "https://inbox.dpdk.org/dev/20231123151344.162812-16-juraj.linkes@pantheon.tech",
    "date": "2023-11-23T15:13:38",
    "name": "[v8,15/21] dts: os session docstring update",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": true,
    "hash": "81879467c16c156e5849e0fe1f6a6f468bd2e569",
    "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/20231123151344.162812-16-juraj.linkes@pantheon.tech/mbox/",
    "series": [
        {
            "id": 30375,
            "url": "https://patches.dpdk.org/api/series/30375/?format=api",
            "web_url": "https://patches.dpdk.org/project/dpdk/list/?series=30375",
            "date": "2023-11-23T15:13:23",
            "name": "dts: docstrings update",
            "version": 8,
            "mbox": "https://patches.dpdk.org/series/30375/mbox/"
        }
    ],
    "comments": "https://patches.dpdk.org/api/patches/134581/comments/",
    "check": "success",
    "checks": "https://patches.dpdk.org/api/patches/134581/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 377CB433AC;\n\tThu, 23 Nov 2023 16:16:11 +0100 (CET)",
            "from mails.dpdk.org (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id 0484E4328B;\n\tThu, 23 Nov 2023 16:14:24 +0100 (CET)",
            "from mail-wm1-f52.google.com (mail-wm1-f52.google.com\n [209.85.128.52]) by mails.dpdk.org (Postfix) with ESMTP id EA22C42FFB\n for <dev@dpdk.org>; Thu, 23 Nov 2023 16:14:06 +0100 (CET)",
            "by mail-wm1-f52.google.com with SMTP id\n 5b1f17b1804b1-40839652b97so6268995e9.3\n for <dev@dpdk.org>; Thu, 23 Nov 2023 07:14:06 -0800 (PST)",
            "from jlinkes-PT-Latitude-5530.. ([84.245.121.10])\n by smtp.gmail.com with ESMTPSA id\n q4-20020adfea04000000b003296b488961sm1870143wrm.31.2023.11.23.07.14.05\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Thu, 23 Nov 2023 07:14:06 -0800 (PST)"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=pantheon.tech; s=google; t=1700752446; x=1701357246; 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=NWs3KzhDflxN0byCnr6b/OQ3hAoOKSw+CnBBJeMLuY4=;\n b=Qnb3gJzaXXhzhQ6e6i1TUPjhvrpvdP5GL98jVWwGYOxPweBjjArYirvFZIAcYBmHeo\n 2OVGEkUSNPvTx8fE9KF9V87gLGTheqpxc730nYQMYyraTDHnw4o/yV1/5VVVR5rfV0Dz\n AzKm0MzPHG96yGG3/+r7Lpu5+A4WqEE4K9r9UVvGWHyVjP53BpWG36+uhrjl0zEbGxdh\n ARfQ33UJ001NpZWdtmXOIrWYZ3nLSW2NHVUxPz/F4JErbDwhfcH6d3TPZE8qvrQP1CzS\n tYTr/9O/BXcPB7dxE6beQzJx14Fubf0OhXVkpzZM3WVV5AiZKwRoMv/knftAsYeDRDZ8\n TaXg==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20230601; t=1700752446; x=1701357246;\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=NWs3KzhDflxN0byCnr6b/OQ3hAoOKSw+CnBBJeMLuY4=;\n b=lkRa4Z7EHRAOag5Io9I4yr2vQUx+4d5HmY3zlZ3PcFbI98E3apOAYFnhWQiJ9q0vca\n NyZuBel+LQV54vw9ziMaTNVJaghI1kKFBUIKcE8qr4HQSMbuJHVr9JjOMZCsmlV9RCA4\n ZXk8nhcQJ1oPaHDiZ5XszhwFaoOhVwhh89h/CQ7Zu4srUGdemidFkpNNVouZqJfnP2Ov\n u+faUFU/gD3K5XKPr+RQArczBu7avFDI6cRxg2mRu9Z0u37MGQrEnWedTDaFTciz6RSs\n VO6GvOg/YOvSuJZ34apX/KqmiNtjOQWPWTmwXhzHDnImjC5sqF+vzsEDgIN588ZZECcK\n VE4A==",
        "X-Gm-Message-State": "AOJu0YxeBhHEzv+SY21oBD7utqs68tHZSSv/G0ULpzY9sR/zmk2mOmnP\n 5I0aijgGZWH7Kpnx3A1DL/Q9Pw==",
        "X-Google-Smtp-Source": "\n AGHT+IE/kPGwfO5uPxVJ3I5jzMCDpeboCo3vq7yqNSGMUZCBOqbEhgYGP3Qsd17pXGqGIRFr4wk4SQ==",
        "X-Received": "by 2002:a05:600c:3587:b0:409:6edc:6e5c with SMTP id\n p7-20020a05600c358700b004096edc6e5cmr4775144wmq.0.1700752446533;\n Thu, 23 Nov 2023 07:14:06 -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 v8 15/21] dts: os session docstring update",
        "Date": "Thu, 23 Nov 2023 16:13:38 +0100",
        "Message-Id": "<20231123151344.162812-16-juraj.linkes@pantheon.tech>",
        "X-Mailer": "git-send-email 2.34.1",
        "In-Reply-To": "<20231123151344.162812-1-juraj.linkes@pantheon.tech>",
        "References": "<20231115130959.39420-1-juraj.linkes@pantheon.tech>\n <20231123151344.162812-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..cfdbd1c4bd 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 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 :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": [
        "v8",
        "15/21"
    ]
}