From patchwork Thu Aug 19 17:38:25 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Niteesh G. S." X-Patchwork-Id: 12447683 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.5 required=3.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED,DKIM_INVALID,DKIM_SIGNED,FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8AD4BC4338F for ; Thu, 19 Aug 2021 17:42:43 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 1805D6108E for ; Thu, 19 Aug 2021 17:42:43 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.1 mail.kernel.org 1805D6108E Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=nongnu.org Received: from localhost ([::1]:55040 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mGm3u-0005om-40 for qemu-devel@archiver.kernel.org; Thu, 19 Aug 2021 13:42:42 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:42376) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mGm0f-00039m-Nx for qemu-devel@nongnu.org; Thu, 19 Aug 2021 13:39:21 -0400 Received: from mail-pl1-x636.google.com ([2607:f8b0:4864:20::636]:40575) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1mGm0e-0000cx-4A for qemu-devel@nongnu.org; Thu, 19 Aug 2021 13:39:21 -0400 Received: by mail-pl1-x636.google.com with SMTP id c4so4327539plh.7 for ; Thu, 19 Aug 2021 10:39:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=YsaEKAl33eCFR1bSTiVOxhOZ86zefKNazCSX24D4aT0=; b=jpKFGSqLcNqUWyCfAcwkZibJ0JsdxdE2CifIUuV2m6Sg2QZOrCOVdPCvgeJM5eytCe qYTiuamAw3bBMsLDgWye/R/abgk5o4XZAliOytWIOiJlltjtRRgVV38WgdpILgY3nVYQ gGxnYD+eXs544BKV2L4tQLU4jihW6mhHDcR7jB2ahEf9dFptLhj5X6CPKddyLobSdLti aDlHb0scD+/oAGdyENfDy+TaKLiG4IlgACUnnr5z7ayGScrYhNSNBZzxZ23Q01GDEcTd tZeD6iFnV9kkDTgyf0zgXuX12liX9WoryEAWMh0MtkUF4Zq8v3W0ASQ8ZYfBMzcmYIkW mclA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=YsaEKAl33eCFR1bSTiVOxhOZ86zefKNazCSX24D4aT0=; b=sW0LSdupG2gQNAXbMzfl0U0/ArOAPk7x1qhoiz5DqP3pwkuARkcCH2jpF3bBDhCfqG KV+wR6DrjAPWB0Rwn0ajXFIXXfrfwOmsMyD0mfG7xAdRDF0SJ7pp1hRyKS6dpmmwz4xH 64S+OOFnM1RpkLoohkeZchQxY1qHbTtAE/k5EqkODpW6MMr7cPTIC77BhR78cCw6i1/L XQYyQpmvaIHDvuGAJ7mfFL0HWbcK/fhauLq/aDU6XsPw361UarnjsxWijnMalo9DImHf HpgvI9H+lrjGmqv+Lo7O0tk67iYCIFtLrFshCOUhShkgk6bSLY3HFlv2srobzNj1xacj OZqA== X-Gm-Message-State: AOAM532PdCFgbE9ucOhYDv45ODvfMkc1iZ5zok+SoE2mT4QHhyhBiK9K tdNSQ3x0lW2fwE+M/yjA31J+alJ7rxE= X-Google-Smtp-Source: ABdhPJxFqQ5webvTCUrCazdICjuRvPWkf4vh0qYPWsbh1CTVWfxy88yiMX4AJbW8oBvIW/SXdOtogw== X-Received: by 2002:a17:90a:de16:: with SMTP id m22mr16127271pjv.38.1629394758660; Thu, 19 Aug 2021 10:39:18 -0700 (PDT) Received: from localhost.localdomain ([120.138.12.8]) by smtp.gmail.com with ESMTPSA id s2sm4061090pfw.193.2021.08.19.10.39.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Aug 2021 10:39:18 -0700 (PDT) From: G S Niteesh Babu To: qemu-devel@nongnu.org Subject: [PATCH v4 1/7] python: disable pylint errors for aqmp-tui Date: Thu, 19 Aug 2021 23:08:25 +0530 Message-Id: <20210819173831.23515-2-niteesh.gs@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20210819173831.23515-1-niteesh.gs@gmail.com> References: <20210819173831.23515-1-niteesh.gs@gmail.com> Received-SPF: pass client-ip=2607:f8b0:4864:20::636; envelope-from=niteesh.gs@gmail.com; helo=mail-pl1-x636.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: ehabkost@redhat.com, kchamart@redhat.com, jsnow@redhat.com, armbru@redhat.com, wainersm@redhat.com, G S Niteesh Babu , stefanha@redhat.com, crosa@redhat.com, eblake@redhat.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" Disable missing-docstring and fixme pylint warnings. This is because since the AQMP is just a prototype it is currently not documented properly and lot of todo and fixme's are still in place. Signed-off-by: G S Niteesh Babu --- python/setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/setup.cfg b/python/setup.cfg index 077395f96e..e83c88db2c 100644 --- a/python/setup.cfg +++ b/python/setup.cfg @@ -90,6 +90,8 @@ ignore_missing_imports = True # --disable=W". disable=too-many-function-args, # mypy handles this with less false positives. no-member, # mypy also handles this better. + missing-docstring, # FIXME + fixme, # FIXME [pylint.basic] # Good variable names which should always be accepted, separated by a comma. From patchwork Thu Aug 19 17:38:26 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Niteesh G. S." X-Patchwork-Id: 12447697 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.5 required=3.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED,DKIM_INVALID,DKIM_SIGNED,FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 038DDC4338F for ; Thu, 19 Aug 2021 17:45:22 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id B576D6109F for ; Thu, 19 Aug 2021 17:45:21 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.1 mail.kernel.org B576D6109F Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=nongnu.org Received: from localhost ([::1]:34246 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mGm6S-0002Ut-N1 for qemu-devel@archiver.kernel.org; Thu, 19 Aug 2021 13:45:20 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:42392) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mGm0j-0003Fy-C5 for qemu-devel@nongnu.org; Thu, 19 Aug 2021 13:39:25 -0400 Received: from mail-pl1-x62e.google.com ([2607:f8b0:4864:20::62e]:44755) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1mGm0h-0000f9-Mx for qemu-devel@nongnu.org; Thu, 19 Aug 2021 13:39:25 -0400 Received: by mail-pl1-x62e.google.com with SMTP id q2so4312912plr.11 for ; Thu, 19 Aug 2021 10:39:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=uKACz5OqsekNHFIwRU3Lz9Hd8OaVdD7SYAY+WghaoVk=; b=S2M1JaH8zOkD4j0IADktN2iLoxFbumRZIB8v5zFUXlrghQZSx5Jtz+Idp/CZ6LhNDr oSlrLPsafGvZ4pM6vrdEo+9SASh+3Qnsbe6vBdODY+tBPzkxXBlqqjHPWsEwsRDmI6wd sb4Uvq8P0IjedzzM0yvsm5IuZaHRgm91jyLsBrwcTUH/l7B51ln+p/qeI7zUi2KDPimS Ost4fZNUPphx4pD5Bz8ab4Xd/HG/7MWf8Z1uqZmhCrgm4ahFM4JXn5ikb1V9xdcSjbsE 55UZUA4qJ7EvTHwcCWIUmmF4P83O0v9seulvu+DJl1ewS7aDzh+OEwDr2FQmzBte7xu/ 02xQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=uKACz5OqsekNHFIwRU3Lz9Hd8OaVdD7SYAY+WghaoVk=; b=ECngsYQSbeUiqd/9Mv6oNlFQPiBtryVs4+NWQdiVCCNTKo+RC+LLbjCRfjU2Uc8yPC B5zvpKv7Ga3gsZm0eDY42hP3mOesc2L0qbhzDBY3NpH2FP5q+Ps8P7B3/KYSnMrkOts6 axLHNNudzvKgxBpLx0SW3hb+RHWLxjbbDnhxNipJf0FdZ0KtaVtp6BbuZcwcJcKHtlhN jjX3jvehxv+M4aF6EsWGlwZVFU1z0G1bXyFXGemX/Yzoiq+AbtvmJc+RsgzkFlAvKIij u274G9vo2Yy9aTjvEzdZmUbC/lNvjxDh97u7sLmAGh4+3Qapfs3o8+dM8GK/xLMZBIQc y9ZQ== X-Gm-Message-State: AOAM533Ohi0EeBNu9zaHAD1yu30Scj5lsF+RDvtAhwEKhe5IhGAJlZNl svhPlciwnPGRAnwOWdAy1gbxOCIx39w= X-Google-Smtp-Source: ABdhPJx5+4KosVBQXq8lskonCj4a/0OTTyTFThUXnjmv6U0jWWazyjEL+n/Obfyrd7NhxHDi5xz8Dw== X-Received: by 2002:a17:90a:7061:: with SMTP id f88mr8521205pjk.121.1629394762227; Thu, 19 Aug 2021 10:39:22 -0700 (PDT) Received: from localhost.localdomain ([120.138.12.8]) by smtp.gmail.com with ESMTPSA id s2sm4061090pfw.193.2021.08.19.10.39.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Aug 2021 10:39:22 -0700 (PDT) From: G S Niteesh Babu To: qemu-devel@nongnu.org Subject: [PATCH v4 2/7] python: Add dependencies for AQMP TUI Date: Thu, 19 Aug 2021 23:08:26 +0530 Message-Id: <20210819173831.23515-3-niteesh.gs@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20210819173831.23515-1-niteesh.gs@gmail.com> References: <20210819173831.23515-1-niteesh.gs@gmail.com> Received-SPF: pass client-ip=2607:f8b0:4864:20::62e; envelope-from=niteesh.gs@gmail.com; helo=mail-pl1-x62e.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: ehabkost@redhat.com, kchamart@redhat.com, jsnow@redhat.com, armbru@redhat.com, wainersm@redhat.com, G S Niteesh Babu , stefanha@redhat.com, crosa@redhat.com, eblake@redhat.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" Added dependencies for the upcoming AQMP TUI under the optional 'tui' group. The same dependencies have also been added under the devel group since no work around has been found for optional groups to imply other optional groups. Signed-off-by: G S Niteesh Babu Reviewed-by: John Snow --- python/Pipfile.lock | 12 ++++++++++++ python/setup.cfg | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/python/Pipfile.lock b/python/Pipfile.lock index 457f5c3fe8..da7a4ee164 100644 --- a/python/Pipfile.lock +++ b/python/Pipfile.lock @@ -289,6 +289,18 @@ "markers": "python_version < '3.8'", "version": "==3.10.0.0" }, + "urwid": { + "hashes": [ + "sha256:588bee9c1cb208d0906a9f73c613d2bd32c3ed3702012f51efe318a3f2127eae" + ], + "version": "==2.1.2" + }, + "urwid-readline": { + "hashes": [ + "sha256:018020cbc864bb5ed87be17dc26b069eae2755cb29f3a9c569aac3bded1efaf4" + ], + "version": "==0.13" + }, "virtualenv": { "hashes": [ "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467", diff --git a/python/setup.cfg b/python/setup.cfg index e83c88db2c..a0ed3279d8 100644 --- a/python/setup.cfg +++ b/python/setup.cfg @@ -44,11 +44,18 @@ devel = mypy >= 0.770 pylint >= 2.8.0 tox >= 3.18.0 + urwid >= 2.1.2 + urwid-readline >= 0.13 # Provides qom-fuse functionality fuse = fusepy >= 2.0.4 +# AQMP TUI dependencies +tui = + urwid >= 2.1.2 + urwid-readline >= 0.13 + [options.entry_points] console_scripts = qom = qemu.qmp.qom:main @@ -133,5 +140,6 @@ allowlist_externals = make deps = .[devel] .[fuse] # Workaround to trigger tox venv rebuild + .[tui] # Workaround to trigger tox venv rebuild commands = make check From patchwork Thu Aug 19 17:38:27 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Niteesh G. S." X-Patchwork-Id: 12447687 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-18.5 required=3.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED,DKIM_INVALID,DKIM_SIGNED,FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,MENTIONS_GIT_HOSTING,SPF_HELO_NONE,SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1A766C4320A for ; Thu, 19 Aug 2021 17:42:49 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 947976108E for ; Thu, 19 Aug 2021 17:42:48 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.1 mail.kernel.org 947976108E Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=nongnu.org Received: from localhost ([::1]:55546 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mGm3z-0006EQ-OM for qemu-devel@archiver.kernel.org; Thu, 19 Aug 2021 13:42:47 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:42414) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mGm0q-0003Jg-87 for qemu-devel@nongnu.org; Thu, 19 Aug 2021 13:39:33 -0400 Received: from mail-pf1-x42a.google.com ([2607:f8b0:4864:20::42a]:43836) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1mGm0m-0000lS-1s for qemu-devel@nongnu.org; Thu, 19 Aug 2021 13:39:31 -0400 Received: by mail-pf1-x42a.google.com with SMTP id 7so6181872pfl.10 for ; Thu, 19 Aug 2021 10:39:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=NWAY7tZUF1UG+SLZWRn+ZIqqB2YoXxKZMPfN1jNqRDo=; b=Umz8ndqmhYVQvMwLRADp1J2C9VVs5L5aXwrgKCTECxNzQ2q7OZa5wTCR+ZUHVe8UYr /Wo+9cJnkfs2nD8QHLwvSJcQ2KEBnF0flDmL6bxnNfzV8uzT2/W+WfeEMyvRs1MPxf2L mSQqdPOoJ5RcKbxCw7CXWRcgiIIyR3Dn50i9XVz8dzeyeEpNTws/V+XwhQyRnC0Wf+fp JeqLDzUoxhyPo8be5zgE7OqUkm8LS+5pjQDU3yYHy/zlFPNrJ/JvLbc6qZiKjOGnXUhC +y2l6Wto0dAgznvgGqtOEEpNZBwAWmFtlhqO8tJMTGKn+ozktWRdVIAfulrjCE86HikN hMew== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=NWAY7tZUF1UG+SLZWRn+ZIqqB2YoXxKZMPfN1jNqRDo=; b=dpp8Fpt3CoZSi4ic+fZG+XFBRKpWSl5tn0JA30XS8TlNh6RAj+VB1mxQk2WVhScmMm BC565dGEaFXEHnadsn5j7WAHjniMbHE3Q1eKzLaapsq6PRME3TI973YdE0fFq7SdC8Mb f1OS/AYiJPjQ1CKG/R8+vgt0b5VyEwCu3v7GmoOmm9Abi1/9KY0Kh5pgxkg35hIDTngx qqfE2GR8bC8aEnwgTzxEqG8CaD79+/pdzQQ1BLm7zv/6zUGW27ZCr3d1zP63+nEurjyE bWjrpMYUfRUYwzKCr/sO5DZo+E2IJfNPxPLAOiWVbVXdjuRq8VcIx0izl01FpZTrfGWu oKDw== X-Gm-Message-State: AOAM530J2B/UA/+/fA+aUjvAL9FfjjrVvZPT/Unpih9djJi74eGFlnH6 0jpPX81W4hzSqn3p2y43De0eJZ+tnz0= X-Google-Smtp-Source: ABdhPJyPriwV3i+GNb/D7oKuQNk6znWZunUc1f6YUvUhh4OaJAOBj4eRkffplTI0Heu8OXzkBbw9YQ== X-Received: by 2002:a65:4682:: with SMTP id h2mr15257812pgr.409.1629394766529; Thu, 19 Aug 2021 10:39:26 -0700 (PDT) Received: from localhost.localdomain ([120.138.12.8]) by smtp.gmail.com with ESMTPSA id s2sm4061090pfw.193.2021.08.19.10.39.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Aug 2021 10:39:26 -0700 (PDT) From: G S Niteesh Babu To: qemu-devel@nongnu.org Subject: [PATCH v4 3/7] python/aqmp-tui: Add AQMP TUI draft Date: Thu, 19 Aug 2021 23:08:27 +0530 Message-Id: <20210819173831.23515-4-niteesh.gs@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20210819173831.23515-1-niteesh.gs@gmail.com> References: <20210819173831.23515-1-niteesh.gs@gmail.com> Received-SPF: pass client-ip=2607:f8b0:4864:20::42a; envelope-from=niteesh.gs@gmail.com; helo=mail-pf1-x42a.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: ehabkost@redhat.com, kchamart@redhat.com, jsnow@redhat.com, armbru@redhat.com, wainersm@redhat.com, G S Niteesh Babu , stefanha@redhat.com, crosa@redhat.com, eblake@redhat.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" Added a draft of AQMP TUI. Implements the follwing basic features: 1) Command transmission/reception. 2) Shows events asynchronously. 3) Shows server status in the bottom status bar. Also added type annotations and necessary pylint, mypy configurations Signed-off-by: G S Niteesh Babu --- python/qemu/aqmp/aqmp_tui.py | 566 +++++++++++++++++++++++++++++++++++ python/setup.cfg | 15 +- 2 files changed, 579 insertions(+), 2 deletions(-) create mode 100644 python/qemu/aqmp/aqmp_tui.py diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py new file mode 100644 index 0000000000..12c9c4162a --- /dev/null +++ b/python/qemu/aqmp/aqmp_tui.py @@ -0,0 +1,566 @@ +# Copyright (c) 2021 +# +# Authors: +# Niteesh Babu G S +# +# This work is licensed under the terms of the GNU GPL, version 2 or +# later. See the COPYING file in the top-level directory. +""" +AQMP TUI + +AQMP TUI is an asynchronous interface built on top the of the AQMP library. +It is the successor of QMP-shell and is bought-in as a replacement for it. + +Example Usage: aqmp-tui +Full Usage: aqmp-tui --help +""" + +import argparse +import asyncio +import logging +from logging import Handler, LogRecord +import signal +from typing import ( + List, + Optional, + Tuple, + Type, + Union, + cast, +) + +import urwid +import urwid_readline + +from ..qmp import QEMUMonitorProtocol, QMPBadPortError +from .message import DeserializationError, Message, UnexpectedTypeError +from .protocol import ConnectError +from .qmp_client import ExecInterruptedError, QMPClient +from .util import create_task, pretty_traceback + + +# The name of the signal that is used to update the history list +UPDATE_MSG: str = 'UPDATE_MSG' + + +def format_json(msg: str) -> str: + """ + Formats given multi-line JSON message into a single-line message. + Converting into single line is more asthetically pleasing when looking + along with error messages. + + Eg: + Input: + [ 1, + true, + 3 ] + The above input is not a valid QMP message and produces the following error + "QMP message is not a JSON object." + When displaying this in TUI in multiline mode we get + + [ 1, + true, + 3 ]: QMP message is not a JSON object. + + whereas in singleline mode we get the following + + [1, true, 3]: QMP message is not a JSON object. + + The single line mode is more asthetically pleasing. + + :param msg: + The message to formatted into single line. + + :return: Formatted singleline message. + + NOTE: We cannot use the JSON module here because it is only capable of + format valid JSON messages. But here the goal is to also format invalid + JSON messages. + """ + msg = msg.replace('\n', '') + words = msg.split(' ') + words = [word for word in words if word != ''] + return ' '.join(words) + + +def has_tui_handler(logger: logging.Logger, + handler_type: Type[Handler]) -> bool: + """ + The Logger class has no interface to check if a certain type of handler is + installed or not. So we provide an interface to do so. + + :param logger: + Logger object + :param handler_type: + The type of the handler to be checked. + + :return: returns True if handler of type `handler_type` is installed else + False. + """ + handlers = logger.handlers + for handler in handlers: + if isinstance(handler, handler_type): + return True + return False + + +class App(QMPClient): + """ + Implements the AQMP TUI. + + Initializes the widgets and starts the urwid event loop. + """ + def __init__(self, address: Union[str, Tuple[str, int]]) -> None: + """ + Initializes the TUI. + + :param address: + Address of the server to connect to. + """ + urwid.register_signal(type(self), UPDATE_MSG) + self.window = Window(self) + self.address = address + self.aloop: Optional[asyncio.AbstractEventLoop] = None + super().__init__() + + def add_to_history(self, msg: str, level: Optional[str] = None) -> None: + """ + Appends the msg to the history list. + + :param msg: + The raw message to be appended in string type. + """ + urwid.emit_signal(self, UPDATE_MSG, msg, level) + + def _cb_outbound(self, msg: Message) -> Message: + """ + Callback: outbound message hook. + + Appends the outgoing messages to the history box. + + :param msg: raw outbound message. + :return: final outbound message. + """ + str_msg = str(msg) + + if not has_tui_handler(logging.getLogger(), TUILogHandler): + logging.debug('Request: %s', str_msg) + self.add_to_history('<-- ' + str_msg) + return msg + + def _cb_inbound(self, msg: Message) -> Message: + """ + Callback: outbound message hook. + + Appends the incoming messages to the history box. + + :param msg: raw inbound message. + :return: final inbound message. + """ + str_msg = str(msg) + + if not has_tui_handler(logging.getLogger(), TUILogHandler): + logging.debug('Request: %s', str_msg) + self.add_to_history('--> ' + str_msg) + return msg + + def handle_event(self, event: Message) -> None: + """ + Handles the event. + + :param event: + The event to be handled. + """ + # TODO: Consider all states present in qapi/run-state.json + if event['event'] == 'SHUTDOWN': + self._set_status('[Server Shutdown]') + + async def wait_for_events(self) -> None: + """ + This coroutine continously waits for events and dispatches them. + """ + async for event in self.events: + self.handle_event(event) + + async def _send_to_server(self, msg: Message) -> None: + """ + This coroutine sends the message to the server. + The message has to be pre-validated. + + :param msg: + Pre-validated message to be to sent to the server. + + :raise Exception: When an unhandled exception is caught. + """ + try: + await self._raw(msg, assign_id='id' not in msg) + except ExecInterruptedError: + logging.info('Error server disconnected before reply') + self.add_to_history('Server disconnected before reply', 'ERROR') + self._set_status("[Server Disconnected]") + except Exception as err: + logging.error('Exception from _send_to_server: %s', str(err)) + raise err + + def cb_send_to_server(self, raw_msg: str) -> None: + """ + Validates and sends the message to the server. + The raw string message is first converted into a Message object + and is then sent to the server. + + :param raw_msg: + The raw string message to be sent to the server. + + :raise Exception: When an unhandled exception is caught. + """ + try: + raw_msg = format_json(raw_msg) + msg = Message(bytes(raw_msg, encoding='utf-8')) + create_task(self._send_to_server(msg)) + except (ValueError, TypeError) as err: + logging.info('Invalid message: %s', str(err)) + self.add_to_history(f'{raw_msg}: {err}') + except (DeserializationError, UnexpectedTypeError) as err: + logging.info('Invalid message: %s', err.error_message) + self.add_to_history(f'{raw_msg}: {err.error_message}') + + def unhandled_input(self, key: str) -> None: + """ + Handle's keys which haven't been handled by the child widgets. + + :param key: + Unhandled key + """ + if key == 'esc': + self.kill_app() + + def kill_app(self) -> None: + """ + Initiates killing of app. A bridge between asynchronous and synchronous + code. + """ + create_task(self._kill_app()) + + async def _kill_app(self) -> None: + """ + This coroutine initiates the actual disconnect process and calls + urwid.ExitMainLoop() to kill the TUI. + + :raise Exception: When an unhandled exception is caught. + """ + # It is ok to call disconnect even in disconnect state + try: + await self.disconnect() + logging.debug('Disconnect finished. Exiting app') + except EOFError: + # We receive an EOF during disconnect, ignore that + pass + except Exception as err: + logging.info('_kill_app: %s', str(err)) + # Let the app crash after providing a proper stack trace + raise err + raise urwid.ExitMainLoop() + + def _set_status(self, msg: str) -> None: + """ + Sets the message as the status. + + :param msg: + The message to be displayed in the status bar. + """ + self.window.footer.set_text(msg) + + def _get_formatted_address(self) -> str: + """ + Returns a formatted version of the server's address. + + :return: formatted address + """ + if isinstance(self.address, tuple): + host, port = self.address + addr = f'{host}:{port}' + else: + addr = f'{self.address}' + return addr + + async def connect_server(self) -> None: + """ + Initiates a connection to the server at address `self.address` + and in case of a failure, sets the status to the respective error. + """ + try: + await self.connect(self.address) + addr = self._get_formatted_address() + self._set_status(f'Connected to {addr}') + except ConnectError as err: + logging.info('connect_server: ConnectError %s', str(err)) + self._set_status(f'[ConnectError: {err.error_message}]') + + def run(self, debug: bool = False) -> None: + """ + Starts the long running co-routines and the urwid event loop. + + :param debug: + Enables/Disables asyncio event loop debugging + """ + self.aloop = asyncio.get_event_loop() + self.aloop.set_debug(debug) + + # Gracefully handle SIGTERM and SIGINT signals + cancel_signals = [signal.SIGTERM, signal.SIGINT] + for sig in cancel_signals: + self.aloop.add_signal_handler(sig, self.kill_app) + + event_loop = urwid.AsyncioEventLoop(loop=self.aloop) + main_loop = urwid.MainLoop(urwid.AttrMap(self.window, 'background'), + unhandled_input=self.unhandled_input, + handle_mouse=True, + event_loop=event_loop) + + create_task(self.wait_for_events(), self.aloop) + create_task(self.connect_server(), self.aloop) + try: + main_loop.run() + except Exception as err: + logging.error('%s\n%s\n', str(err), pretty_traceback()) + raise err + + +class StatusBar(urwid.Text): + """ + A simple statusbar modelled using the Text widget. The status can be + set using the set_text function. All text set is aligned to right. + """ + def __init__(self, text: str = ''): + super().__init__(text, align='right') + + +class Editor(urwid_readline.ReadlineEdit): + """ + A simple editor modelled using the urwid_readline.ReadlineEdit widget. + Mimcs GNU readline shortcuts and provides history support. + + The readline shortcuts can be found below: + https://github.com/rr-/urwid_readline#features + + Along with the readline features, this editor also has support for + history. Pressing the 'up' arrow key with empty message box, lists the + previous message inplace. + + Currently there is no support to save the history to a file. The history of + previous commands is lost on exit. + """ + def __init__(self, master: App) -> None: + """ + Initializes the editor widget + + :param master: Reference to the TUI object. + """ + super().__init__(caption='> ', multiline=True) + self.master = master + self.history: List[str] = [] + self.last_index: int = -1 + self.show_history: bool = False + + def keypress(self, size: Tuple[int, int], key: str) -> Optional[str]: + """ + Handles the keypress on this widget. + + :param size: + The current size of the widget. + :param key: + The key to be handled. + + :return: Unhandled key if any. + """ + # TODO: Add some logic for down key and clean up logic if possible. + # Returning None means the key has been handled by this widget + # which otherwise is propogated to the parent widget to be + # handled + msg = self.get_edit_text() + if key == 'up' and not msg: + # Show the history when 'up arrow' is pressed with no input text. + # NOTE: The show_history logic is necessary because in 'multiline' + # mode (which we use) 'up arrow' is used to move between lines. + self.show_history = True + last_msg = self.history[self.last_index] if self.history else '' + self.set_edit_text(last_msg) + self.edit_pos = len(last_msg) + self.last_index += 1 + elif key == 'up' and self.show_history: + if self.last_index < len(self.history): + self.set_edit_text(self.history[self.last_index]) + self.edit_pos = len(self.history[self.last_index]) + self.last_index += 1 + elif key == 'meta enter': + # When using multiline, enter inserts a new line into the editor + # send the input to the server on alt + enter + self.master.cb_send_to_server(msg) + self.history.insert(0, msg) + self.set_edit_text('') + self.last_index = 0 + self.show_history = False + else: + self.show_history = False + self.last_index = 0 + return cast(Optional[str], super().keypress(size, key)) + return None + + +class EditorWidget(urwid.Filler): + """ + The Editor is a flow widget and has to wrapped inside a box widget. + This class wraps the Editor inside filler widget. + """ + def __init__(self, master: App) -> None: + super().__init__(Editor(master), valign='top') + + +class HistoryBox(urwid.ListBox): + """ + This widget is modelled using the ListBox widget, contains the list of + all messages both QMP messages and log messsages to be shown in the TUI. + + The messages are urwid.Text widgets. On every append of a message, the + focus is shifted to the last appended message. + """ + def __init__(self, master: App) -> None: + """ + Initializes the historybox widget + + :param master: Reference to the TUI object. + """ + self.master = master + self.history = urwid.SimpleFocusListWalker([]) + super().__init__(self.history) + + def add_to_history(self, history: str) -> None: + """ + Appends a message to the list and set the focus to the last appended + message. + + :param history: + The history item(message/event) to be appended to the list. + """ + self.history.append(urwid.Text(history)) + if self.history: + self.history.set_focus(len(self.history) - 1) + + +class HistoryWindow(urwid.Frame): + """ + This window composes the HistoryBox and EditorWidget in a horizontal split. + By default the first focus is given to the history box. + """ + def __init__(self, master: App) -> None: + """ + Initializes this widget and its child widgets. + + :param master: Reference to the TUI object. + """ + self.master = master + self.editor_widget = EditorWidget(master) + self.editor = urwid.LineBox(self.editor_widget) + self.history = HistoryBox(master) + self.body = urwid.Pile([('weight', 80, self.history), + ('weight', 20, self.editor)]) + super().__init__(self.body) + urwid.connect_signal(self.master, UPDATE_MSG, self.cb_add_to_history) + + def cb_add_to_history(self, msg: str, level: Optional[str] = None) -> None: + """ + Appends a message to the history box + + :param msg: + The message to be appended to the history box. + """ + if level: + msg = f'[{level}]: {msg}' + self.history.add_to_history(msg) + + +class Window(urwid.Frame): + """ + This window is the top most widget of the TUI and will contain other + windows. Each window is responsible for displaying a specific + functionality. + For eg: The history window is responsible for showing the history of + messages and the editor. + """ + def __init__(self, master: App) -> None: + """ + Initializes this widget and its child windows. + + :param master: Reference to the TUI object. + """ + self.master = master + footer = StatusBar() + body = HistoryWindow(master) + super().__init__(body, footer=footer) + + +class TUILogHandler(Handler): + """ + This handler routes all the log messages to the TUI screen. + It is installed to the root logger to so that the log message from all + libraries begin used is routed to the screen. + """ + def __init__(self, tui: App) -> None: + """ + Initializes the handler class. + + :param tui: + Reference to the TUI object. + """ + super().__init__() + self.tui = tui + + def emit(self, record: LogRecord) -> None: + """ + Emits a record to the TUI screen. + + Appends the log message to the TUI screen + """ + level = record.levelname + msg = record.getMessage() + self.tui.add_to_history(msg, level) + + +def main() -> None: + """ + Driver of the whole script, parses arguments, initialize the TUI and + the logger. + """ + parser = argparse.ArgumentParser(description='AQMP TUI') + parser.add_argument('qmp_server', help='Address of the QMP server. ' + 'Format ') + parser.add_argument('--log-file', help='The Log file name') + parser.add_argument('--log-level', default='WARNING', + help='Log level ') + parser.add_argument('--asyncio-debug', action='store_true', + help='Enable debug mode for asyncio loop. ' + 'Generates lot of output, makes TUI unusable when ' + 'logs are logged in the TUI. ' + 'Use only when logging to a file.') + args = parser.parse_args() + + try: + address = QEMUMonitorProtocol.parse_address(args.qmp_server) + except QMPBadPortError as err: + parser.error(str(err)) + + app = App(address) + + root_logger = logging.getLogger() + root_logger.setLevel(logging.getLevelName(args.log_level)) + + if args.log_file: + root_logger.addHandler(logging.FileHandler(args.log_file)) + else: + root_logger.addHandler(TUILogHandler(app)) + + app.run(args.asyncio_debug) + + +if __name__ == '__main__': + main() diff --git a/python/setup.cfg b/python/setup.cfg index a0ed3279d8..1ff2b907a2 100644 --- a/python/setup.cfg +++ b/python/setup.cfg @@ -81,8 +81,19 @@ namespace_packages = True # fusepy has no type stubs: allow_subclassing_any = True +[mypy-qemu.aqmp.aqmp_tui] +# urwid and urwid_readline have no type stubs: +allow_subclassing_any = True + +# The following missing import directives are because these libraries do not +# provide type stubs. Allow them on an as-needed basis for mypy. [mypy-fuse] -# fusepy has no type stubs: +ignore_missing_imports = True + +[mypy-urwid] +ignore_missing_imports = True + +[mypy-urwid_readline] ignore_missing_imports = True [pylint.messages control] @@ -97,7 +108,7 @@ ignore_missing_imports = True # --disable=W". disable=too-many-function-args, # mypy handles this with less false positives. no-member, # mypy also handles this better. - missing-docstring, # FIXME + # missing-docstring, # FIXME fixme, # FIXME [pylint.basic] From patchwork Thu Aug 19 17:38:28 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Niteesh G. S." X-Patchwork-Id: 12447685 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.5 required=3.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED,DKIM_INVALID,DKIM_SIGNED,FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id E8113C4338F for ; Thu, 19 Aug 2021 17:42:48 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 99942610A3 for ; Thu, 19 Aug 2021 17:42:48 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.1 mail.kernel.org 99942610A3 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=nongnu.org Received: from localhost ([::1]:55620 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mGm3z-0006H1-OU for qemu-devel@archiver.kernel.org; Thu, 19 Aug 2021 13:42:47 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:42482) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mGm13-0003Rl-O6 for qemu-devel@nongnu.org; Thu, 19 Aug 2021 13:39:46 -0400 Received: from mail-pg1-x532.google.com ([2607:f8b0:4864:20::532]:42577) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1mGm0w-0000v8-Tk for qemu-devel@nongnu.org; Thu, 19 Aug 2021 13:39:43 -0400 Received: by mail-pg1-x532.google.com with SMTP id o2so6574298pgr.9 for ; Thu, 19 Aug 2021 10:39:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=3Y0xnQlCQMo4Htg0UKH12TeCNltRyFFwKPt8huwMb4A=; b=DF6GSwJ2N66ch7ND2KTzCZ99c0n+eYrYMjKqUWEOj9r0lO/yrs2Dpaec8OxPvT0IMy u1NVHFM2v6zDOiLBB3gupL46h/l4jjGxLcH8z9drNGslBHmnl3cWOzrJuM8l0jTSyufP UmoPiLlfluGuUxax2ZlxY3n90fFROF9IrC9yV08oQBkY8Ej4/rvyt8DgNamn6fJeCauh Zl3tcGMqCZtisrVywL7Mydrh5iWPYk6D0vY048iZ959s6TKyvGAbMTco6H1kDg0XSzlk P0dZj8AN8rPaN2qSpr9CX3cX3APomgKohQOdb03NuY5OFD2O8b9fWwK3apqYrLo64qqS tc0w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=3Y0xnQlCQMo4Htg0UKH12TeCNltRyFFwKPt8huwMb4A=; b=A3o7MboItSzRFyvNkb2zXUcHgUvSC/C9dklMGqo1jR9kQP6tQaYiZPIDN5fZlQFfxW GT+nhkX9LWMhd35WO0QqbRAJ8u8bM/Pm5q5pUGCq9KbEjRKb7Ko6AYvij3SzubsD8faj ycy67QdMtoFjd18kVkWiAKdBgVjK9DwDjOL7eQ1VXeXmhY682r3S13pqYVlEmZ/69FRA bKp8cgAHlwNlr6svX8AHCKA0auym7ZfY3/d/ZfIRlCnRRyiZvKsr1xnJlXDaSzWkQD1v +3NQog/3CvLcaZJGLHHZ3Xqe/0hvhsIekN0M/RNl2LagG9veWPFyInNaL1xMk2TAVhjo uH7w== X-Gm-Message-State: AOAM531F56bght+wQS9SZ8QH7y+Wzy4Xyqq7cjJQpWyxIPH0YKkGLQPs xkj2hfOsyoOUAPSCRXDInszFK8qSBkM= X-Google-Smtp-Source: ABdhPJwlCVmMiVuiGe0x93pywN2ydFN3zYjOkcnIVsFjRcnwByo3Ciy1VbO4+qcjyr7yiGst5oztWg== X-Received: by 2002:a63:a511:: with SMTP id n17mr14916375pgf.156.1629394770493; Thu, 19 Aug 2021 10:39:30 -0700 (PDT) Received: from localhost.localdomain ([120.138.12.8]) by smtp.gmail.com with ESMTPSA id s2sm4061090pfw.193.2021.08.19.10.39.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Aug 2021 10:39:30 -0700 (PDT) From: G S Niteesh Babu To: qemu-devel@nongnu.org Subject: [PATCH v4 4/7] python: Add entry point for aqmp-tui Date: Thu, 19 Aug 2021 23:08:28 +0530 Message-Id: <20210819173831.23515-5-niteesh.gs@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20210819173831.23515-1-niteesh.gs@gmail.com> References: <20210819173831.23515-1-niteesh.gs@gmail.com> Received-SPF: pass client-ip=2607:f8b0:4864:20::532; envelope-from=niteesh.gs@gmail.com; helo=mail-pg1-x532.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: ehabkost@redhat.com, kchamart@redhat.com, jsnow@redhat.com, armbru@redhat.com, wainersm@redhat.com, G S Niteesh Babu , stefanha@redhat.com, crosa@redhat.com, eblake@redhat.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" Add an entry point for aqmp-tui. This will allow it to be run from the command line using "aqmp-tui localhost:1234" More options available in the TUI can be found using "aqmp-tui -h" Signed-off-by: G S Niteesh Babu --- python/setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/python/setup.cfg b/python/setup.cfg index 1ff2b907a2..64ed0be0a7 100644 --- a/python/setup.cfg +++ b/python/setup.cfg @@ -66,6 +66,7 @@ console_scripts = qom-fuse = qemu.qmp.qom_fuse:QOMFuse.entry_point [fuse] qemu-ga-client = qemu.qmp.qemu_ga_client:main qmp-shell = qemu.qmp.qmp_shell:main + aqmp-tui = qemu.aqmp.aqmp_tui:main [tui] [flake8] extend-ignore = E722 # Prefer pylint's bare-except checks to flake8's From patchwork Thu Aug 19 17:38:29 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Niteesh G. S." X-Patchwork-Id: 12447699 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.5 required=3.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED,DKIM_INVALID,DKIM_SIGNED,FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 94B8FC4338F for ; Thu, 19 Aug 2021 17:45:28 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 618866109F for ; Thu, 19 Aug 2021 17:45:28 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.1 mail.kernel.org 618866109F Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=nongnu.org Received: from localhost ([::1]:34674 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mGm6Z-0002lg-KL for qemu-devel@archiver.kernel.org; Thu, 19 Aug 2021 13:45:27 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:42492) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mGm14-0003Sv-IE for qemu-devel@nongnu.org; Thu, 19 Aug 2021 13:39:46 -0400 Received: from mail-pf1-x435.google.com ([2607:f8b0:4864:20::435]:42661) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1mGm11-0000x4-G2 for qemu-devel@nongnu.org; Thu, 19 Aug 2021 13:39:46 -0400 Received: by mail-pf1-x435.google.com with SMTP id 18so6179036pfh.9 for ; Thu, 19 Aug 2021 10:39:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=bfGJ7tGClvQrhd++AO2QAx+QBlxrMwpmjVO+pr4JCRo=; b=vF/kwKu5LbTeC4lfCl9A/GQzukH+m81piOtggR5gk5ngLKZGW69ak1S0NEndZzkcuh 5t2PlqOBKXGfKnK4uT+/VS2k0iZT3WbCGOLdLgzlmdADr2FaThEdIKud47g5HgU2jp37 fsvWpu9WWg2ymP7/UCt71XtOGeyN0MQWK+Km8suWy/uWymHwQX7Qdo4B4hmGSCBHULhg LKvDDUDonl5QEFhnBRYPKbQaGZskuSgZgRWRhFqdRXEiW0bnf9vJDqnxtY2IEDZw2N3z 1EvcxjpFuTrK6Shill3ucdq/ScFP3VqR+EVmZ7CVskUsOkZYSlcnaNsPs1sg0vDeb9ot 3Oeg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=bfGJ7tGClvQrhd++AO2QAx+QBlxrMwpmjVO+pr4JCRo=; b=UdxPZLHM+ROZz4MU8spUCllrN/P2p6ByGJJQl4wEN5WD5SL9iom3D0SeIruMfkfqVK lWKcBL0mfi3q4cYALu1Q41vKPo4/nvnM49Ixrg6PoF3+XFjTW59ZPYl8oIprqQkrWYkK VpN6iqb4IrkyCGffVoTATFafX65K6GS976ljWzlEmMjJJpDsx3mM+fbcjj0mvjhcm57r GsuwhgRU3UzrTAksGm2cozGWszGJ5rCeOM6+mXT7cUp0sbQbJThfCRQH9AnifZ/ekLJq NY4KTVckaAfBfb0QyZOc1oLckP7Ac2yRZ5Ckz+dVw78gkZJTcK2hfRAdFxZ0uKqJbBF0 hBew== X-Gm-Message-State: AOAM532dZtp+jvgECStmqoFLgg9t0Ebtm6wU9zpUIMS1YMCAfFfbYVGu 9TBcr7M0d+s0RdIvdd6eg9U8OYnZEoA= X-Google-Smtp-Source: ABdhPJxvRmA0Z9Jj3Fj4WPnVc4vR0WzZOOU92SVOCHdwIX8HqclrR1blBlu9CyHtQ/QO2kqLM3Z1cw== X-Received: by 2002:a63:6ca:: with SMTP id 193mr14941452pgg.265.1629394781087; Thu, 19 Aug 2021 10:39:41 -0700 (PDT) Received: from localhost.localdomain ([120.138.12.8]) by smtp.gmail.com with ESMTPSA id s2sm4061090pfw.193.2021.08.19.10.39.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Aug 2021 10:39:40 -0700 (PDT) From: G S Niteesh Babu To: qemu-devel@nongnu.org Subject: [PATCH v4 5/7] python: add optional pygments dependency Date: Thu, 19 Aug 2021 23:08:29 +0530 Message-Id: <20210819173831.23515-6-niteesh.gs@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20210819173831.23515-1-niteesh.gs@gmail.com> References: <20210819173831.23515-1-niteesh.gs@gmail.com> Received-SPF: pass client-ip=2607:f8b0:4864:20::435; envelope-from=niteesh.gs@gmail.com; helo=mail-pf1-x435.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: ehabkost@redhat.com, kchamart@redhat.com, jsnow@redhat.com, armbru@redhat.com, wainersm@redhat.com, G S Niteesh Babu , stefanha@redhat.com, crosa@redhat.com, eblake@redhat.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" Added pygments as optional dependency for AQMP TUI. This is required for the upcoming syntax highlighting feature in AQMP TUI. The dependency has also been added in the devel optional group. Added mypy 'ignore_missing_imports' for pygments since it does not have any type stubs. Signed-off-by: G S Niteesh Babu --- python/Pipfile.lock | 8 ++++++++ python/setup.cfg | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/python/Pipfile.lock b/python/Pipfile.lock index da7a4ee164..d2a7dbd88b 100644 --- a/python/Pipfile.lock +++ b/python/Pipfile.lock @@ -200,6 +200,14 @@ ], "version": "==2.0.0" }, + "pygments": { + "hashes": [ + "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f", + "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e" + ], + "markers": "python_version >= '3.5'", + "version": "==2.9.0" + }, "pylint": { "hashes": [ "sha256:082a6d461b54f90eea49ca90fff4ee8b6e45e8029e5dbd72f6107ef84f3779c0", diff --git a/python/setup.cfg b/python/setup.cfg index 64ed0be0a7..1c2e879a4c 100644 --- a/python/setup.cfg +++ b/python/setup.cfg @@ -46,6 +46,7 @@ devel = tox >= 3.18.0 urwid >= 2.1.2 urwid-readline >= 0.13 + Pygments >= 2.9.0 # Provides qom-fuse functionality fuse = @@ -55,6 +56,7 @@ fuse = tui = urwid >= 2.1.2 urwid-readline >= 0.13 + Pygments >= 2.9.0 [options.entry_points] console_scripts = @@ -97,6 +99,9 @@ ignore_missing_imports = True [mypy-urwid_readline] ignore_missing_imports = True +[mypy-pygments] +ignore_missing_imports = True + [pylint.messages control] # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this From patchwork Thu Aug 19 17:38:30 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Niteesh G. S." X-Patchwork-Id: 12447703 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.5 required=3.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED,DKIM_INVALID,DKIM_SIGNED,FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id CC471C4338F for ; Thu, 19 Aug 2021 17:47:24 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 7CBF96056C for ; Thu, 19 Aug 2021 17:47:24 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.1 mail.kernel.org 7CBF96056C Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=nongnu.org Received: from localhost ([::1]:39036 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mGm8R-0005q9-Lp for qemu-devel@archiver.kernel.org; Thu, 19 Aug 2021 13:47:23 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:42504) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mGm16-0003Ym-Dz for qemu-devel@nongnu.org; Thu, 19 Aug 2021 13:39:48 -0400 Received: from mail-pf1-x435.google.com ([2607:f8b0:4864:20::435]:33733) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1mGm14-0000yf-Kr for qemu-devel@nongnu.org; Thu, 19 Aug 2021 13:39:48 -0400 Received: by mail-pf1-x435.google.com with SMTP id w68so6166139pfd.0 for ; Thu, 19 Aug 2021 10:39:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=Ohg0RB/7GAQ6fdsZqyzTEEb3r+rqjjQ4s2JLAWGkK68=; b=fdo3tfUnju0m4Bl4rqaM/XawzhA0Lh5yTW7thFr0e4UY9AcELDaiezFL3vWkEnxgxh Sd9tS5GbyBXY3j2bw+uhSAXerhGyEL0YQ1Mxn0pnUak3HRKAbbSyaXk3vdWmXjP1aDIn MIbjyjXVq1aXhGBuMk4sVGd1m/ofzqhR2Sl4EmYexxge9pItHgJQ4lZ01RWHEIL+i4Lx Fi0c6Q9ygyOAEs644MzfQ8gA/mGvk5ZlTYo1Tj1AENx3Oo11X0mNTDlSrfgUBYM4T+Up GyUyOYmMb75H1lDc4TPBrKZ9jUs6mYet86eRonx1RVkshByHJtuWjdSwazlAlypur1gA LTjw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=Ohg0RB/7GAQ6fdsZqyzTEEb3r+rqjjQ4s2JLAWGkK68=; b=K9Lq8z3HiXAYnUTG+eg0DMpys8St+97jyuVGGG0tp/9EzSzZt99wGV1zz0mMtUMaqc PZGj0dHEesmHuFAHeRTYQq28aSrs7DN9XPktec7KBrtiQ1eKxCB99tQKB2kpyeDBMvJU 3UrBhcy2RxaoZsYHK0bs09r6AOVFy6d9hm6n9/U1W5sBb4TPAe9rxDbRKhfCHjOUuKz5 gqIPaIEksSrWZPAXT2iFw9dWO6ujDSWJbYVvPBqeTtqI3syhIi7Q/5Y/lod/SPumFzbk zZu1NGVEwwJGO4NX127wH8iLFFi5PD1Wd4crz/hz+2eG3n573pSH8GSAuRZvwOFM09Cs rhyA== X-Gm-Message-State: AOAM531A6J9jmnodq06icdbMwuDms++il4vbpgeMn9mewSPWCFu4lkp/ kCJpzkK8h6kmLuqmErBKZ5vlbZKQrEw= X-Google-Smtp-Source: ABdhPJwiN4ObDicDtpSgeXtF5YcQ6FADvHvexJ7HE9P22EXtSHL/YAa44Fp5hFBbwLnle/8wcNjXvw== X-Received: by 2002:a63:5f15:: with SMTP id t21mr15075767pgb.391.1629394785077; Thu, 19 Aug 2021 10:39:45 -0700 (PDT) Received: from localhost.localdomain ([120.138.12.8]) by smtp.gmail.com with ESMTPSA id s2sm4061090pfw.193.2021.08.19.10.39.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Aug 2021 10:39:44 -0700 (PDT) From: G S Niteesh Babu To: qemu-devel@nongnu.org Subject: [PATCH v4 6/7] python/aqmp-tui: Add syntax highlighting Date: Thu, 19 Aug 2021 23:08:30 +0530 Message-Id: <20210819173831.23515-7-niteesh.gs@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20210819173831.23515-1-niteesh.gs@gmail.com> References: <20210819173831.23515-1-niteesh.gs@gmail.com> Received-SPF: pass client-ip=2607:f8b0:4864:20::435; envelope-from=niteesh.gs@gmail.com; helo=mail-pf1-x435.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: ehabkost@redhat.com, kchamart@redhat.com, jsnow@redhat.com, armbru@redhat.com, wainersm@redhat.com, G S Niteesh Babu , stefanha@redhat.com, crosa@redhat.com, eblake@redhat.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" Add syntax highlighting for the incoming and outgoing QMP messages. This is achieved using the pygments module which was added in a previous commit. The current implementation is a really simple one which doesn't allow for any configuration. In future this has to be improved to allow for easier theme config using an external config of some sort. Signed-off-by: G S Niteesh Babu --- python/qemu/aqmp/aqmp_tui.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py index 12c9c4162a..03d4808acd 100644 --- a/python/qemu/aqmp/aqmp_tui.py +++ b/python/qemu/aqmp/aqmp_tui.py @@ -29,6 +29,8 @@ cast, ) +from pygments import lexers +from pygments import token as Token import urwid import urwid_readline @@ -43,6 +45,22 @@ UPDATE_MSG: str = 'UPDATE_MSG' +palette = [ + (Token.Punctuation, '', '', '', 'h15,bold', 'g7'), + (Token.Text, '', '', '', '', 'g7'), + (Token.Name.Tag, '', '', '', 'bold,#f88', 'g7'), + (Token.Literal.Number.Integer, '', '', '', '#fa0', 'g7'), + (Token.Literal.String.Double, '', '', '', '#6f6', 'g7'), + (Token.Keyword.Constant, '', '', '', '#6af', 'g7'), + ('DEBUG', '', '', '', '#ddf', 'g7'), + ('INFO', '', '', '', 'g100', 'g7'), + ('WARNING', '', '', '', '#ff6', 'g7'), + ('ERROR', '', '', '', '#a00', 'g7'), + ('CRITICAL', '', '', '', '#a00', 'g7'), + ('background', '', 'black', '', '', 'g7'), +] + + def format_json(msg: str) -> str: """ Formats given multi-line JSON message into a single-line message. @@ -303,6 +321,9 @@ def run(self, debug: bool = False) -> None: :param debug: Enables/Disables asyncio event loop debugging """ + screen = urwid.raw_display.Screen() + screen.set_terminal_properties(256) + self.aloop = asyncio.get_event_loop() self.aloop.set_debug(debug) @@ -314,6 +335,8 @@ def run(self, debug: bool = False) -> None: event_loop = urwid.AsyncioEventLoop(loop=self.aloop) main_loop = urwid.MainLoop(urwid.AttrMap(self.window, 'background'), unhandled_input=self.unhandled_input, + screen=screen, + palette=palette, handle_mouse=True, event_loop=event_loop) @@ -434,7 +457,8 @@ def __init__(self, master: App) -> None: self.history = urwid.SimpleFocusListWalker([]) super().__init__(self.history) - def add_to_history(self, history: str) -> None: + def add_to_history(self, + history: Union[str, List[Tuple[str, str]]]) -> None: """ Appends a message to the list and set the focus to the last appended message. @@ -473,10 +497,18 @@ def cb_add_to_history(self, msg: str, level: Optional[str] = None) -> None: :param msg: The message to be appended to the history box. + :param level: + The log level of the message, if it is a log message. """ + formatted = [] if level: msg = f'[{level}]: {msg}' - self.history.add_to_history(msg) + formatted.append((level, msg)) + else: + lexer = lexers.JsonLexer() # pylint: disable=no-member + for token in lexer.get_tokens(msg): + formatted.append(token) + self.history.add_to_history(formatted) class Window(urwid.Frame): From patchwork Thu Aug 19 17:38:31 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Niteesh G. S." X-Patchwork-Id: 12447701 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.5 required=3.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED,DKIM_INVALID,DKIM_SIGNED,FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id E544CC4338F for ; Thu, 19 Aug 2021 17:45:34 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 8889C6108E for ; Thu, 19 Aug 2021 17:45:34 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.1 mail.kernel.org 8889C6108E Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=nongnu.org Received: from localhost ([::1]:34756 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mGm6f-0002ok-OO for qemu-devel@archiver.kernel.org; Thu, 19 Aug 2021 13:45:33 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:42530) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mGm1G-0003iG-4F for qemu-devel@nongnu.org; Thu, 19 Aug 2021 13:39:58 -0400 Received: from mail-pj1-x102f.google.com ([2607:f8b0:4864:20::102f]:34362) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1mGm18-00010v-H8 for qemu-devel@nongnu.org; Thu, 19 Aug 2021 13:39:57 -0400 Received: by mail-pj1-x102f.google.com with SMTP id gz13-20020a17090b0ecdb0290178c0e0ce8bso7645912pjb.1 for ; Thu, 19 Aug 2021 10:39:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=9H1tvnadPOE3ZsVFX3l0X96lrMuTkp945GJK/1OZbEY=; b=UmbE9a8pHb8jItBVEMb9It5w49PRoPuwtJZ4dyMWBbvPfVI4Te/EP74w34C/xAdFNC 6ifmdTXdwxw7xoc0HSGGoe/zvNwF+uT97JHQtUYEzSWIPczu/Tqnb9tJksXXc1nm+KrL V8qiunKH7FQR69l72+gjyImbJw86s1KgD+tIL6LKGWe0Msz11uu2nBKaiCboWrTwOJ7e ywqwi+AzONvaNDDsnx7lmd09o9ckacrLnHOasfuK3tvU/CYTyxj72sUex/f8uU7Cm2lp MrK8UmNSjMR1uRIP9nbImsM2ph80xbPXi9nRq7SbBSU32N89kbGL3CbcX6iml1qFAsXp aofQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=9H1tvnadPOE3ZsVFX3l0X96lrMuTkp945GJK/1OZbEY=; b=ME+UCyne07Q4Vvy5KtHPDyVP/YZz8XSsStVOno3gfusuf1QYBnwYYp3dmyMeP4DjdY 1qRFMZ0IdowXQC1QwTiZ3dHs6T50C7HYLtKhJlCQ8GNL3HcmmQAaPSvwQa1KcYb+i4// Yg9HJrZTIADDXL8w60cB9tTEC+YyiGjzq3j8EVFOytxJ54NbrhCR2uxjkeWF/FvVRRR8 7Vj3AUqZMUUoN18ovEiF77IQURHVFAzwUgTnH6CI73u+8uLVXMBGM54jPV/GtSotafmx ppdec7S52Nx2uxnYZ7BFk77Rxz83oqjhjIqsSs0d/NJmVlepcX/ZOVTXc/Y+Hl2QpLa7 n7LQ== X-Gm-Message-State: AOAM5335Z/L0qUGK8U2CVfEZSOP1HitV3KxTwjlSRQlATML2OLBu2v+V tzSwYlsLjQrCf9Kksxs6xD9MKcD86A8= X-Google-Smtp-Source: ABdhPJxbUlwtslofLF1Tacxt9Ju58jyQqKzA02ZRso3WYNE58xUCrQSVIjD6TguAIgxhJW96GcxUIQ== X-Received: by 2002:a17:90a:a01:: with SMTP id o1mr15824838pjo.115.1629394788983; Thu, 19 Aug 2021 10:39:48 -0700 (PDT) Received: from localhost.localdomain ([120.138.12.8]) by smtp.gmail.com with ESMTPSA id s2sm4061090pfw.193.2021.08.19.10.39.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Aug 2021 10:39:48 -0700 (PDT) From: G S Niteesh Babu To: qemu-devel@nongnu.org Subject: [PATCH v4 7/7] python/aqmp-tui: Add QMP connection manager Date: Thu, 19 Aug 2021 23:08:31 +0530 Message-Id: <20210819173831.23515-8-niteesh.gs@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20210819173831.23515-1-niteesh.gs@gmail.com> References: <20210819173831.23515-1-niteesh.gs@gmail.com> Received-SPF: pass client-ip=2607:f8b0:4864:20::102f; envelope-from=niteesh.gs@gmail.com; helo=mail-pj1-x102f.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: ehabkost@redhat.com, kchamart@redhat.com, jsnow@redhat.com, armbru@redhat.com, wainersm@redhat.com, G S Niteesh Babu , stefanha@redhat.com, crosa@redhat.com, eblake@redhat.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" The connection manager will take care of connecting/disconnecting to the server. This will also try to reconnect to the server in certain situations where the client has been disconnected due to some error condition. Signed-off-by: G S Niteesh Babu --- python/qemu/aqmp/aqmp_tui.py | 127 +++++++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 22 deletions(-) diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py index 03d4808acd..c47abe0a25 100644 --- a/python/qemu/aqmp/aqmp_tui.py +++ b/python/qemu/aqmp/aqmp_tui.py @@ -35,8 +35,9 @@ import urwid_readline from ..qmp import QEMUMonitorProtocol, QMPBadPortError +from .error import ProtocolError from .message import DeserializationError, Message, UnexpectedTypeError -from .protocol import ConnectError +from .protocol import ConnectError, Runstate from .qmp_client import ExecInterruptedError, QMPClient from .util import create_task, pretty_traceback @@ -128,17 +129,26 @@ class App(QMPClient): Initializes the widgets and starts the urwid event loop. """ - def __init__(self, address: Union[str, Tuple[str, int]]) -> None: + def __init__(self, address: Union[str, Tuple[str, int]], num_retries: int, + retry_delay: Optional[int]) -> None: """ Initializes the TUI. :param address: Address of the server to connect to. + :param num_retries: + The number of times to retry before stopping to reconnect. + :param retry_delay: + The delay(sec) before each retry """ urwid.register_signal(type(self), UPDATE_MSG) self.window = Window(self) self.address = address self.aloop: Optional[asyncio.AbstractEventLoop] = None + self.num_retries = num_retries + self.retry_delay = retry_delay if retry_delay else 2 + self.retry: bool = False + self.disconnecting: bool = False super().__init__() def add_to_history(self, msg: str, level: Optional[str] = None) -> None: @@ -212,10 +222,10 @@ def handle_event(self, event: Message) -> None: """ try: await self._raw(msg, assign_id='id' not in msg) - except ExecInterruptedError: - logging.info('Error server disconnected before reply') + except ExecInterruptedError as err: + logging.info('Error server disconnected before reply %s', str(err)) self.add_to_history('Server disconnected before reply', 'ERROR') - self._set_status("[Server Disconnected]") + await self.disconnect() except Exception as err: logging.error('Exception from _send_to_server: %s', str(err)) raise err @@ -237,10 +247,10 @@ def cb_send_to_server(self, raw_msg: str) -> None: create_task(self._send_to_server(msg)) except (ValueError, TypeError) as err: logging.info('Invalid message: %s', str(err)) - self.add_to_history(f'{raw_msg}: {err}') + self.add_to_history(f'{raw_msg}: {err}', 'ERROR') except (DeserializationError, UnexpectedTypeError) as err: logging.info('Invalid message: %s', err.error_message) - self.add_to_history(f'{raw_msg}: {err.error_message}') + self.add_to_history(f'{raw_msg}: {err.error_message}', 'ERROR') def unhandled_input(self, key: str) -> None: """ @@ -266,18 +276,32 @@ def kill_app(self) -> None: :raise Exception: When an unhandled exception is caught. """ - # It is ok to call disconnect even in disconnect state + await self.disconnect() + logging.debug('Disconnect finished. Exiting app') + raise urwid.ExitMainLoop() + + async def disconnect(self) -> None: + """ + Overrides the disconnect method to handle the errors locally. + """ + if self.disconnecting: + return try: - await self.disconnect() - logging.debug('Disconnect finished. Exiting app') - except EOFError: - # We receive an EOF during disconnect, ignore that - pass + self.disconnecting = True + await super().disconnect() + self.retry = False + except EOFError as err: + logging.info('disconnect: %s', str(err)) + self.retry = True + except ProtocolError as err: + logging.info('disconnect: %s', str(err)) + self.retry = False except Exception as err: - logging.info('_kill_app: %s', str(err)) - # Let the app crash after providing a proper stack trace + logging.error('disconnect: Unhandled exception %s', str(err)) + self.retry = False raise err - raise urwid.ExitMainLoop() + finally: + self.disconnecting = False def _set_status(self, msg: str) -> None: """ @@ -301,18 +325,72 @@ def _get_formatted_address(self) -> str: addr = f'{self.address}' return addr - async def connect_server(self) -> None: + async def _initiate_connection(self) -> Optional[ConnectError]: + """ + Tries connecting to a server a number of times with a delay between + each try. If all retries failed then return the error faced during + the last retry. + + :return: Error faced during last retry. + """ + current_retries = 0 + err = None + + # initial try + await self.connect_server() + while self.retry and current_retries < self.num_retries: + logging.info('Connection Failed, retrying in %d', self.retry_delay) + status = f'[Retry #{current_retries} ({self.retry_delay}s)]' + self._set_status(status) + + await asyncio.sleep(self.retry_delay) + + err = await self.connect_server() + current_retries += 1 + # If all retries failed report the last error + if err: + logging.info('All retries failed: %s', err) + return err + return None + + async def manage_connection(self) -> None: + """ + Manage the connection based on the current run state. + + A reconnect is issued when the current state is IDLE and the number + of retries is not exhausted. + A disconnect is issued when the current state is DISCONNECTING. + """ + while True: + if self.runstate == Runstate.IDLE: + err = await self._initiate_connection() + # If retry is still true then, we have exhausted all our tries. + if err: + self._set_status(f'[Error: {err.error_message}]') + else: + addr = self._get_formatted_address() + self._set_status(f'[Connected {addr}]') + elif self.runstate == Runstate.DISCONNECTING: + self._set_status('[Disconnected]') + await self.disconnect() + # check if a retry is needed + if self.runstate == Runstate.IDLE: + continue + await self.runstate_changed() + + async def connect_server(self) -> Optional[ConnectError]: """ Initiates a connection to the server at address `self.address` and in case of a failure, sets the status to the respective error. """ try: await self.connect(self.address) - addr = self._get_formatted_address() - self._set_status(f'Connected to {addr}') + self.retry = False except ConnectError as err: logging.info('connect_server: ConnectError %s', str(err)) - self._set_status(f'[ConnectError: {err.error_message}]') + self.retry = True + return err + return None def run(self, debug: bool = False) -> None: """ @@ -341,7 +419,7 @@ def run(self, debug: bool = False) -> None: event_loop=event_loop) create_task(self.wait_for_events(), self.aloop) - create_task(self.connect_server(), self.aloop) + create_task(self.manage_connection(), self.aloop) try: main_loop.run() except Exception as err: @@ -566,6 +644,11 @@ def main() -> None: parser = argparse.ArgumentParser(description='AQMP TUI') parser.add_argument('qmp_server', help='Address of the QMP server. ' 'Format ') + parser.add_argument('--num-retries', type=int, default=10, + help='Number of times to reconnect before giving up.') + parser.add_argument('--retry-delay', type=int, + help='Time(s) to wait before next retry. ' + 'Default action is to wait 2s between each retry.') parser.add_argument('--log-file', help='The Log file name') parser.add_argument('--log-level', default='WARNING', help='Log level ') @@ -581,7 +664,7 @@ def main() -> None: except QMPBadPortError as err: parser.error(str(err)) - app = App(address) + app = App(address, args.num_retries, args.retry_delay) root_logger = logging.getLogger() root_logger.setLevel(logging.getLevelName(args.log_level))