get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

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

{
    "id": 119480,
    "url": "http://patches.dpdk.org/api/patches/119480/?format=api",
    "web_url": "http://patches.dpdk.org/project/dpdk/patch/20221104110523.511367-4-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": "<20221104110523.511367-4-juraj.linkes@pantheon.tech>",
    "list_archive_url": "https://inbox.dpdk.org/dev/20221104110523.511367-4-juraj.linkes@pantheon.tech",
    "date": "2022-11-04T11:05:17",
    "name": "[v8,3/9] dts: add config parser module",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": true,
    "hash": "908c3edcda20e7b002dd003f06629f66bb50b7a3",
    "submitter": {
        "id": 1626,
        "url": "http://patches.dpdk.org/api/people/1626/?format=api",
        "name": "Juraj Linkeš",
        "email": "juraj.linkes@pantheon.tech"
    },
    "delegate": {
        "id": 1,
        "url": "http://patches.dpdk.org/api/users/1/?format=api",
        "username": "tmonjalo",
        "first_name": "Thomas",
        "last_name": "Monjalon",
        "email": "thomas@monjalon.net"
    },
    "mbox": "http://patches.dpdk.org/project/dpdk/patch/20221104110523.511367-4-juraj.linkes@pantheon.tech/mbox/",
    "series": [
        {
            "id": 25575,
            "url": "http://patches.dpdk.org/api/series/25575/?format=api",
            "web_url": "http://patches.dpdk.org/project/dpdk/list/?series=25575",
            "date": "2022-11-04T11:05:14",
            "name": "dts: ssh connection to a node",
            "version": 8,
            "mbox": "http://patches.dpdk.org/series/25575/mbox/"
        }
    ],
    "comments": "http://patches.dpdk.org/api/patches/119480/comments/",
    "check": "success",
    "checks": "http://patches.dpdk.org/api/patches/119480/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 794EFA00C5;\n\tFri,  4 Nov 2022 12:05:43 +0100 (CET)",
            "from [217.70.189.124] (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id 38CE442D27;\n\tFri,  4 Nov 2022 12:05:36 +0100 (CET)",
            "from lb.pantheon.sk (lb.pantheon.sk [46.229.239.20])\n by mails.dpdk.org (Postfix) with ESMTP id 162AA42D23\n for <dev@dpdk.org>; Fri,  4 Nov 2022 12:05:34 +0100 (CET)",
            "from localhost (localhost [127.0.0.1])\n by lb.pantheon.sk (Postfix) with ESMTP id 1FE381B68D8;\n Fri,  4 Nov 2022 12:05:33 +0100 (CET)",
            "from lb.pantheon.sk ([127.0.0.1])\n by localhost (lb.pantheon.sk [127.0.0.1]) (amavisd-new, port 10024)\n with ESMTP id U5kweRVLMsdC; Fri,  4 Nov 2022 12:05:31 +0100 (CET)",
            "from entguard.lab.pantheon.local (unknown [46.229.239.141])\n by lb.pantheon.sk (Postfix) with ESMTP id 7115C1BA5A9;\n Fri,  4 Nov 2022 12:05:27 +0100 (CET)"
        ],
        "X-Virus-Scanned": "amavisd-new at siecit.sk",
        "From": "=?utf-8?q?Juraj_Linke=C5=A1?= <juraj.linkes@pantheon.tech>",
        "To": "thomas@monjalon.net, Honnappa.Nagarahalli@arm.com, ohilyard@iol.unh.edu,\n lijuan.tu@intel.com, kda@semihalf.com, bruce.richardson@intel.com",
        "Cc": "dev@dpdk.org, =?utf-8?q?Juraj_Linke=C5=A1?= <juraj.linkes@pantheon.tech>",
        "Subject": "[PATCH v8 3/9] dts: add config parser module",
        "Date": "Fri,  4 Nov 2022 11:05:17 +0000",
        "Message-Id": "<20221104110523.511367-4-juraj.linkes@pantheon.tech>",
        "X-Mailer": "git-send-email 2.25.1",
        "In-Reply-To": "<20221104110523.511367-1-juraj.linkes@pantheon.tech>",
        "References": "<20221103151934.450887-1-juraj.linkes@pantheon.tech>\n <20221104110523.511367-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": "From: Owen Hilyard <ohilyard@iol.unh.edu>\n\nThe configuration is split into two parts, one defining the parameters\nof the test run and the other defining the topology to be used.\n\nThe format of the configuration is YAML. It is validated according to a\njson schema which also server as detailed documentation of the various\nconfiguration fields. This means that the complete set of allowed values\nare tied to the schema as a source of truth. This enables making changes\nto parts of DTS that interface with config files without a high risk of\nbreaking someone's configuration.\n\nThis configuration system uses immutable objects to represent the\nconfiguration, making IDE/LSP autocomplete work properly.\n\nThere are two ways to specify the configuration file path, an\nenvironment variable or a command line argument, applied in that order.\n\nSigned-off-by: Owen Hilyard <ohilyard@iol.unh.edu>\nSigned-off-by: Juraj Linkeš <juraj.linkes@pantheon.tech>\n---\n dts/conf.yaml                              |  6 ++\n dts/framework/__init__.py                  |  3 +\n dts/framework/config/__init__.py           | 99 ++++++++++++++++++++++\n dts/framework/config/conf_yaml_schema.json | 65 ++++++++++++++\n dts/framework/settings.py                  | 84 ++++++++++++++++++\n 5 files changed, 257 insertions(+)\n create mode 100644 dts/conf.yaml\n create mode 100644 dts/framework/__init__.py\n create mode 100644 dts/framework/config/__init__.py\n create mode 100644 dts/framework/config/conf_yaml_schema.json\n create mode 100644 dts/framework/settings.py",
    "diff": "diff --git a/dts/conf.yaml b/dts/conf.yaml\nnew file mode 100644\nindex 0000000000..75947dc234\n--- /dev/null\n+++ b/dts/conf.yaml\n@@ -0,0 +1,6 @@\n+executions:\n+  - system_under_test: \"SUT 1\"\n+nodes:\n+  - name: \"SUT 1\"\n+    hostname: sut1.change.me.localhost\n+    user: root\ndiff --git a/dts/framework/__init__.py b/dts/framework/__init__.py\nnew file mode 100644\nindex 0000000000..d551ad4bf0\n--- /dev/null\n+++ b/dts/framework/__init__.py\n@@ -0,0 +1,3 @@\n+# SPDX-License-Identifier: BSD-3-Clause\n+# Copyright(c) 2022 PANTHEON.tech s.r.o.\n+# Copyright(c) 2022 University of New Hampshire\ndiff --git a/dts/framework/config/__init__.py b/dts/framework/config/__init__.py\nnew file mode 100644\nindex 0000000000..214be8e7f4\n--- /dev/null\n+++ b/dts/framework/config/__init__.py\n@@ -0,0 +1,99 @@\n+# SPDX-License-Identifier: BSD-3-Clause\n+# Copyright(c) 2010-2021 Intel Corporation\n+# Copyright(c) 2022 University of New Hampshire\n+\n+\"\"\"\n+Generic port and topology nodes configuration file load function\n+\"\"\"\n+\n+import json\n+import os.path\n+import pathlib\n+from dataclasses import dataclass\n+from typing import Any\n+\n+import warlock  # type: ignore\n+import yaml\n+\n+from framework.settings import SETTINGS\n+\n+\n+# Slots enables some optimizations, by pre-allocating space for the defined\n+# attributes in the underlying data structure.\n+#\n+# Frozen makes the object immutable. This enables further optimizations,\n+# and makes it thread safe should we every want to move in that direction.\n+@dataclass(slots=True, frozen=True)\n+class NodeConfiguration:\n+    name: str\n+    hostname: str\n+    user: str\n+    password: str | None\n+\n+    @staticmethod\n+    def from_dict(d: dict) -> \"NodeConfiguration\":\n+        return NodeConfiguration(\n+            name=d[\"name\"],\n+            hostname=d[\"hostname\"],\n+            user=d[\"user\"],\n+            password=d.get(\"password\"),\n+        )\n+\n+\n+@dataclass(slots=True, frozen=True)\n+class ExecutionConfiguration:\n+    system_under_test: NodeConfiguration\n+\n+    @staticmethod\n+    def from_dict(d: dict, node_map: dict) -> \"ExecutionConfiguration\":\n+        sut_name = d[\"system_under_test\"]\n+        assert sut_name in node_map, f\"Unknown SUT {sut_name} in execution {d}\"\n+\n+        return ExecutionConfiguration(\n+            system_under_test=node_map[sut_name],\n+        )\n+\n+\n+@dataclass(slots=True, frozen=True)\n+class Configuration:\n+    executions: list[ExecutionConfiguration]\n+\n+    @staticmethod\n+    def from_dict(d: dict) -> \"Configuration\":\n+        nodes: list[NodeConfiguration] = list(\n+            map(NodeConfiguration.from_dict, d[\"nodes\"])\n+        )\n+        assert len(nodes) > 0, \"There must be a node to test\"\n+\n+        node_map = {node.name: node for node in nodes}\n+        assert len(nodes) == len(node_map), \"Duplicate node names are not allowed\"\n+\n+        executions: list[ExecutionConfiguration] = list(\n+            map(\n+                ExecutionConfiguration.from_dict, d[\"executions\"], [node_map for _ in d]\n+            )\n+        )\n+\n+        return Configuration(executions=executions)\n+\n+\n+def load_config() -> Configuration:\n+    \"\"\"\n+    Loads the configuration file and the configuration file schema,\n+    validates the configuration file, and creates a configuration object.\n+    \"\"\"\n+    with open(SETTINGS.config_file_path, \"r\") as f:\n+        config_data = yaml.safe_load(f)\n+\n+    schema_path = os.path.join(\n+        pathlib.Path(__file__).parent.resolve(), \"conf_yaml_schema.json\"\n+    )\n+\n+    with open(schema_path, \"r\") as f:\n+        schema = json.load(f)\n+    config: dict[str, Any] = warlock.model_factory(schema, name=\"_Config\")(config_data)\n+    config_obj: Configuration = Configuration.from_dict(dict(config))\n+    return config_obj\n+\n+\n+CONFIGURATION = load_config()\ndiff --git a/dts/framework/config/conf_yaml_schema.json b/dts/framework/config/conf_yaml_schema.json\nnew file mode 100644\nindex 0000000000..6b8d6ccd05\n--- /dev/null\n+++ b/dts/framework/config/conf_yaml_schema.json\n@@ -0,0 +1,65 @@\n+{\n+  \"$schema\": \"https://json-schema.org/draft-07/schema\",\n+  \"title\": \"DTS Config Schema\",\n+  \"definitions\": {\n+    \"node_name\": {\n+      \"type\": \"string\",\n+      \"description\": \"A unique identifier for a node\"\n+    }\n+  },\n+  \"type\": \"object\",\n+  \"properties\": {\n+    \"nodes\": {\n+      \"type\": \"array\",\n+      \"items\": {\n+        \"type\": \"object\",\n+        \"properties\": {\n+          \"name\": {\n+            \"type\": \"string\",\n+            \"description\": \"A unique identifier for this node\"\n+          },\n+          \"hostname\": {\n+            \"type\": \"string\",\n+            \"description\": \"A hostname from which the node running DTS can access this node. This can also be an IP address.\"\n+          },\n+          \"user\": {\n+            \"type\": \"string\",\n+            \"description\": \"The user to access this node with.\"\n+          },\n+          \"password\": {\n+            \"type\": \"string\",\n+            \"description\": \"The password to use on this node. Use only as a last resort. SSH keys are STRONGLY preferred.\"\n+          }\n+        },\n+        \"additionalProperties\": false,\n+        \"required\": [\n+          \"name\",\n+          \"hostname\",\n+          \"user\"\n+        ]\n+      },\n+      \"minimum\": 1\n+    },\n+    \"executions\": {\n+      \"type\": \"array\",\n+      \"items\": {\n+        \"type\": \"object\",\n+        \"properties\": {\n+          \"system_under_test\": {\n+            \"$ref\": \"#/definitions/node_name\"\n+          }\n+        },\n+        \"additionalProperties\": false,\n+        \"required\": [\n+          \"system_under_test\"\n+        ]\n+      },\n+      \"minimum\": 1\n+    }\n+  },\n+  \"required\": [\n+    \"executions\",\n+    \"nodes\"\n+  ],\n+  \"additionalProperties\": false\n+}\ndiff --git a/dts/framework/settings.py b/dts/framework/settings.py\nnew file mode 100644\nindex 0000000000..007ab46c32\n--- /dev/null\n+++ b/dts/framework/settings.py\n@@ -0,0 +1,84 @@\n+# SPDX-License-Identifier: BSD-3-Clause\n+# Copyright(c) 2010-2021 Intel Corporation\n+# Copyright(c) 2022 PANTHEON.tech s.r.o.\n+# Copyright(c) 2022 University of New Hampshire\n+\n+import argparse\n+import os\n+from collections.abc import Callable, Iterable, Sequence\n+from dataclasses import dataclass\n+from typing import Any, TypeVar\n+\n+_T = TypeVar(\"_T\")\n+\n+\n+def _env_arg(env_var: str) -> Any:\n+    class _EnvironmentArgument(argparse.Action):\n+        def __init__(\n+            self,\n+            option_strings: Sequence[str],\n+            dest: str,\n+            nargs: str | int | None = None,\n+            const: str | None = None,\n+            default: str = None,\n+            type: Callable[[str], _T | argparse.FileType | None] = None,\n+            choices: Iterable[_T] | None = None,\n+            required: bool = True,\n+            help: str | None = None,\n+            metavar: str | tuple[str, ...] | None = None,\n+        ) -> None:\n+            env_var_value = os.environ.get(env_var)\n+            default = env_var_value or default\n+            super(_EnvironmentArgument, self).__init__(\n+                option_strings,\n+                dest,\n+                nargs=nargs,\n+                const=const,\n+                default=default,\n+                type=type,\n+                choices=choices,\n+                required=required,\n+                help=help,\n+                metavar=metavar,\n+            )\n+\n+        def __call__(\n+            self,\n+            parser: argparse.ArgumentParser,\n+            namespace: argparse.Namespace,\n+            values: Any,\n+            option_string: str = None,\n+        ) -> None:\n+            setattr(namespace, self.dest, values)\n+\n+    return _EnvironmentArgument\n+\n+\n+@dataclass(slots=True, frozen=True)\n+class _Settings:\n+    config_file_path: str\n+\n+\n+def _get_parser() -> argparse.ArgumentParser:\n+    parser = argparse.ArgumentParser(description=\"DPDK test framework.\")\n+\n+    parser.add_argument(\n+        \"--config-file\",\n+        action=_env_arg(\"DTS_CFG_FILE\"),\n+        default=\"conf.yaml\",\n+        required=False,\n+        help=\"[DTS_CFG_FILE] configuration file that describes the test cases, SUTs \"\n+        \"and targets.\",\n+    )\n+\n+    return parser\n+\n+\n+def _get_settings() -> _Settings:\n+    parsed_args = _get_parser().parse_args()\n+    return _Settings(\n+        config_file_path=parsed_args.config_file,\n+    )\n+\n+\n+SETTINGS: _Settings = _get_settings()\n",
    "prefixes": [
        "v8",
        "3/9"
    ]
}