get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

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

{
    "id": 113246,
    "url": "http://patches.dpdk.org/api/patches/113246/?format=api",
    "web_url": "http://patches.dpdk.org/project/dpdk/patch/20220622121448.3304251-2-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": "<20220622121448.3304251-2-juraj.linkes@pantheon.tech>",
    "list_archive_url": "https://inbox.dpdk.org/dev/20220622121448.3304251-2-juraj.linkes@pantheon.tech",
    "date": "2022-06-22T12:14:41",
    "name": "[v1,1/8] dts: add ssh pexpect library",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": true,
    "hash": "0ec3bf8a1465a540ad3cf094c70eb416ca525a1b",
    "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/20220622121448.3304251-2-juraj.linkes@pantheon.tech/mbox/",
    "series": [
        {
            "id": 23695,
            "url": "http://patches.dpdk.org/api/series/23695/?format=api",
            "web_url": "http://patches.dpdk.org/project/dpdk/list/?series=23695",
            "date": "2022-06-22T12:14:40",
            "name": "dts: ssh connection to a node",
            "version": 1,
            "mbox": "http://patches.dpdk.org/series/23695/mbox/"
        }
    ],
    "comments": "http://patches.dpdk.org/api/patches/113246/comments/",
    "check": "success",
    "checks": "http://patches.dpdk.org/api/patches/113246/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 136CBA04FD;\n\tWed, 22 Jun 2022 14:15:08 +0200 (CEST)",
            "from [217.70.189.124] (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id 6FCB34281C;\n\tWed, 22 Jun 2022 14:14:56 +0200 (CEST)",
            "from lb.pantheon.sk (lb.pantheon.sk [46.229.239.20])\n by mails.dpdk.org (Postfix) with ESMTP id 063F640DDB\n for <dev@dpdk.org>; Wed, 22 Jun 2022 14:14:55 +0200 (CEST)",
            "from localhost (localhost [127.0.0.1])\n by lb.pantheon.sk (Postfix) with ESMTP id CB4AE86AAF;\n Wed, 22 Jun 2022 14:14:53 +0200 (CEST)",
            "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 ztH70gwXwrqb; Wed, 22 Jun 2022 14:14:49 +0200 (CEST)",
            "from entguard.lab.pantheon.local (unknown [46.229.239.141])\n by lb.pantheon.sk (Postfix) with ESMTP id 46C6C31469;\n Wed, 22 Jun 2022 14:14:49 +0200 (CEST)"
        ],
        "X-Virus-Scanned": "amavisd-new at siecit.sk",
        "From": "=?utf-8?q?Juraj_Linke=C5=A1?= <juraj.linkes@pantheon.tech>",
        "To": "thomas@monjalon.net, david.marchand@redhat.com, jerinjacobk@gmail.com,\n ronan.randles@intel.com, Honnappa.Nagarahalli@arm.com,\n ohilyard@iol.unh.edu, lijuan.tu@intel.com",
        "Cc": "dev@dpdk.org, =?utf-8?q?Juraj_Linke=C5=A1?= <juraj.linkes@pantheon.tech>",
        "Subject": "[PATCH v1 1/8] dts: add ssh pexpect library",
        "Date": "Wed, 22 Jun 2022 12:14:41 +0000",
        "Message-Id": "<20220622121448.3304251-2-juraj.linkes@pantheon.tech>",
        "X-Mailer": "git-send-email 2.25.1",
        "In-Reply-To": "<20220622121448.3304251-1-juraj.linkes@pantheon.tech>",
        "References": "<20220622121448.3304251-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": "The library uses the pexpect python library and implements connection to\na node and two ways to interact with the node:\n1. Send a string with specified prompt which will be matched after\n   the string has been sent to the node.\n2. Send a command to be executed. No prompt is specified here.\n\nSigned-off-by: Juraj Linkeš <juraj.linkes@pantheon.tech>\n---\n dts/framework/exception.py   |  48 +++++++++\n dts/framework/ssh_pexpect.py | 185 +++++++++++++++++++++++++++++++++++\n dts/framework/utils.py       |  11 +++\n 3 files changed, 244 insertions(+)\n create mode 100644 dts/framework/exception.py\n create mode 100644 dts/framework/ssh_pexpect.py\n create mode 100644 dts/framework/utils.py",
    "diff": "diff --git a/dts/framework/exception.py b/dts/framework/exception.py\nnew file mode 100644\nindex 0000000000..a109dd1fb8\n--- /dev/null\n+++ b/dts/framework/exception.py\n@@ -0,0 +1,48 @@\n+\"\"\"\n+User-defined exceptions used across the framework.\n+\"\"\"\n+\n+\n+class TimeoutException(Exception):\n+\n+    \"\"\"\n+    Command execution timeout.\n+    \"\"\"\n+\n+    def __init__(self, command, output):\n+        self.command = command\n+        self.output = output\n+\n+    def __str__(self):\n+        msg = \"TIMEOUT on %s\" % (self.command)\n+        return msg\n+\n+    def get_output(self):\n+        return self.output\n+\n+\n+class SSHConnectionException(Exception):\n+\n+    \"\"\"\n+    SSH connection error.\n+    \"\"\"\n+\n+    def __init__(self, host):\n+        self.host = host\n+\n+    def __str__(self):\n+        return \"Error trying to connect with %s\" % self.host\n+\n+\n+class SSHSessionDeadException(Exception):\n+\n+    \"\"\"\n+    SSH session is not alive.\n+    It can no longer be used.\n+    \"\"\"\n+\n+    def __init__(self, host):\n+        self.host = host\n+\n+    def __str__(self):\n+        return \"SSH session with %s has been dead\" % self.host\ndiff --git a/dts/framework/ssh_pexpect.py b/dts/framework/ssh_pexpect.py\nnew file mode 100644\nindex 0000000000..bccc6fae94\n--- /dev/null\n+++ b/dts/framework/ssh_pexpect.py\n@@ -0,0 +1,185 @@\n+import time\n+\n+from pexpect import pxssh\n+\n+from .exception import SSHConnectionException, SSHSessionDeadException, TimeoutException\n+from .utils import GREEN, RED\n+\n+\"\"\"\n+Module handles ssh sessions to TG and SUT.\n+Implements send_expect function to send commands and get output data.\n+\"\"\"\n+\n+\n+class SSHPexpect:\n+    def __init__(self, node, username, password):\n+        self.magic_prompt = \"MAGIC PROMPT\"\n+        self.logger = None\n+\n+        self.node = node\n+        self.username = username\n+        self.password = password\n+\n+        self._connect_host()\n+\n+    def _connect_host(self):\n+        \"\"\"\n+        Create connection to assigned node.\n+        \"\"\"\n+        retry_times = 10\n+        try:\n+            if \":\" in self.node:\n+                while retry_times:\n+                    self.ip = self.node.split(\":\")[0]\n+                    self.port = int(self.node.split(\":\")[1])\n+                    self.session = pxssh.pxssh(encoding=\"utf-8\")\n+                    try:\n+                        self.session.login(\n+                            self.ip,\n+                            self.username,\n+                            self.password,\n+                            original_prompt=\"[$#>]\",\n+                            port=self.port,\n+                            login_timeout=20,\n+                            password_regex=r\"(?i)(?:password:)|(?:passphrase for key)|(?i)(password for .+:)\",\n+                        )\n+                    except Exception as e:\n+                        print(e)\n+                        time.sleep(2)\n+                        retry_times -= 1\n+                        print(\"retry %d times connecting...\" % (10 - retry_times))\n+                    else:\n+                        break\n+                else:\n+                    raise Exception(\"connect to %s:%s failed\" % (self.ip, self.port))\n+            else:\n+                self.session = pxssh.pxssh(encoding=\"utf-8\")\n+                self.session.login(\n+                    self.node,\n+                    self.username,\n+                    self.password,\n+                    original_prompt=\"[$#>]\",\n+                    password_regex=r\"(?i)(?:password:)|(?:passphrase for key)|(?i)(password for .+:)\",\n+                )\n+            self.send_expect(\"stty -echo\", \"#\")\n+            self.send_expect(\"stty columns 1000\", \"#\")\n+        except Exception as e:\n+            print(RED(e))\n+            if getattr(self, \"port\", None):\n+                suggestion = (\n+                    \"\\nSuggession: Check if the firewall on [ %s ] \" % self.ip\n+                    + \"is stopped\\n\"\n+                )\n+                print(GREEN(suggestion))\n+\n+            raise SSHConnectionException(self.node)\n+\n+    def init_log(self, logger):\n+        self.logger = logger\n+        self.logger.info(\"ssh %s@%s\" % (self.username, self.node))\n+\n+    def send_expect_base(self, command, expected, timeout):\n+        self.clean_session()\n+        self.session.PROMPT = expected\n+        self.__sendline(command)\n+        self.__prompt(command, timeout)\n+\n+        before = self.get_output_before()\n+        return before\n+\n+    def send_expect(self, command, expected, timeout=15, verify=False):\n+\n+        try:\n+            ret = self.send_expect_base(command, expected, timeout)\n+            if verify:\n+                ret_status = self.send_expect_base(\"echo $?\", expected, timeout)\n+                if not int(ret_status):\n+                    return ret\n+                else:\n+                    self.logger.error(\"Command: %s failure!\" % command)\n+                    self.logger.error(ret)\n+                    return int(ret_status)\n+            else:\n+                return ret\n+        except Exception as e:\n+            print(\n+                RED(\n+                    \"Exception happened in [%s] and output is [%s]\"\n+                    % (command, self.get_output_before())\n+                )\n+            )\n+            raise e\n+\n+    def send_command(self, command, timeout=1):\n+        try:\n+            self.clean_session()\n+            self.__sendline(command)\n+        except Exception as e:\n+            raise e\n+\n+        output = self.get_session_before(timeout=timeout)\n+        self.session.PROMPT = self.session.UNIQUE_PROMPT\n+        self.session.prompt(0.1)\n+\n+        return output\n+\n+    def clean_session(self):\n+        self.get_session_before(timeout=0.01)\n+\n+    def get_session_before(self, timeout=15):\n+        \"\"\"\n+        Get all output before timeout\n+        \"\"\"\n+        self.session.PROMPT = self.magic_prompt\n+        try:\n+            self.session.prompt(timeout)\n+        except Exception as e:\n+            pass\n+\n+        before = self.get_output_all()\n+        self.__flush()\n+\n+        return before\n+\n+    def __flush(self):\n+        \"\"\"\n+        Clear all session buffer\n+        \"\"\"\n+        self.session.buffer = \"\"\n+        self.session.before = \"\"\n+\n+    def __prompt(self, command, timeout):\n+        if not self.session.prompt(timeout):\n+            raise TimeoutException(command, self.get_output_all()) from None\n+\n+    def __sendline(self, command):\n+        if not self.isalive():\n+            raise SSHSessionDeadException(self.node)\n+        if len(command) == 2 and command.startswith(\"^\"):\n+            self.session.sendcontrol(command[1])\n+        else:\n+            self.session.sendline(command)\n+\n+    def get_output_before(self):\n+        if not self.isalive():\n+            raise SSHSessionDeadException(self.node)\n+        before = self.session.before.rsplit(\"\\r\\n\", 1)\n+        if before[0] == \"[PEXPECT]\":\n+            before[0] = \"\"\n+\n+        return before[0]\n+\n+    def get_output_all(self):\n+        output = self.session.before\n+        output.replace(\"[PEXPECT]\", \"\")\n+        return output\n+\n+    def close(self, force=False):\n+        if force is True:\n+            self.session.close()\n+        else:\n+            if self.isalive():\n+                self.session.logout()\n+\n+    def isalive(self):\n+        return self.session.isalive()\ndiff --git a/dts/framework/utils.py b/dts/framework/utils.py\nnew file mode 100644\nindex 0000000000..0ffd992952\n--- /dev/null\n+++ b/dts/framework/utils.py\n@@ -0,0 +1,11 @@\n+# SPDX-License-Identifier: BSD-3-Clause\n+# Copyright(c) 2010-2014 Intel Corporation\n+#\n+\n+\n+def RED(text):\n+    return \"\\x1B[\" + \"31;1m\" + str(text) + \"\\x1B[\" + \"0m\"\n+\n+\n+def GREEN(text):\n+    return \"\\x1B[\" + \"32;1m\" + str(text) + \"\\x1B[\" + \"0m\"\n",
    "prefixes": [
        "v1",
        "1/8"
    ]
}