From patchwork Thu May 16 15:41:12 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakob Meng X-Patchwork-Id: 1936053 X-Patchwork-Delegate: i.maximets@samsung.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=UjXG0f2r; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VgDpv1dxtz20dQ for ; Fri, 17 May 2024 01:41:43 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 361BF8386F; Thu, 16 May 2024 15:41:41 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id C7zAmn2kcnfy; Thu, 16 May 2024 15:41:34 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=2605:bc80:3010:104::8cd3:938; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp1.osuosl.org 4BA6E837BC Authentication-Results: smtp1.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=UjXG0f2r Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp1.osuosl.org (Postfix) with ESMTPS id 4BA6E837BC; Thu, 16 May 2024 15:41:34 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id B6FF0C0DD4; Thu, 16 May 2024 15:41:33 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp2.osuosl.org (smtp2.osuosl.org [140.211.166.133]) by lists.linuxfoundation.org (Postfix) with ESMTP id 74E22C0077 for ; Thu, 16 May 2024 15:41:30 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id 572E7401F3 for ; Thu, 16 May 2024 15:41:30 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp2.osuosl.org ([127.0.0.1]) by localhost (smtp2.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id EMCU6O-9XMIt for ; Thu, 16 May 2024 15:41:28 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.133.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=jmeng@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp2.osuosl.org D363841D09 Authentication-Results: smtp2.osuosl.org; dmarc=pass (p=none dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp2.osuosl.org D363841D09 Authentication-Results: smtp2.osuosl.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=UjXG0f2r Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by smtp2.osuosl.org (Postfix) with ESMTPS id D363841D09 for ; Thu, 16 May 2024 15:41:27 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1715874086; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=X9EarbnjML/0qBZWpEmndYJ7zOr8L3rg9lRhKZgBIdw=; b=UjXG0f2rf+jt9eyVu++i864SQfoqLd3GdDqQJFYjdyo5OGSWhOLn3/5lXZ3UntHG2BSoV0 YpQJEClpO8CqWvTN8SkJ9I18JHAFSobYh+a4CMGcVP64RwEVS9OVu3aT/iOf4fjblsLUMG +dXZsvxo420DPEgBOZYeyUG+PJEIPj4= Received: from mail-wr1-f70.google.com (mail-wr1-f70.google.com [209.85.221.70]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-624-1QBRBE8cNEGvDUd4ftRuJA-1; Thu, 16 May 2024 11:41:25 -0400 X-MC-Unique: 1QBRBE8cNEGvDUd4ftRuJA-1 Received: by mail-wr1-f70.google.com with SMTP id ffacd0b85a97d-34e01a857a6so4223371f8f.2 for ; Thu, 16 May 2024 08:41:25 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1715874084; x=1716478884; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=X9EarbnjML/0qBZWpEmndYJ7zOr8L3rg9lRhKZgBIdw=; b=c0cFJw3leaeERygAAfyDg1CGK0M78p9HtC/lRrAFdn3NX2H1CvUgh1Z3MSI9NX1Rea t7nqGoGSyp5ptB0VSx2ihAnHzp24d4iG7s8Jj/g7qkDCwZBy6Le7yr3mDE/JEStVTROx T1SavLUB4Md/Hmqvzu5DL6pm5vUhe8aJyEBN8s+pr8SfgCtXvqxlo5AbSjcXK7Q+E/xj up/lnD6kLMkUyYX2rOg2NavNkPHlmagZpyGNx+gZQBKlW4X79hyO6jqbRasqvGfK3dD8 h8u7bcWB+G+gFA0G73KFDEUinBxk+70/iSxQJZTTnD7xbBFx62QSuimwUn57ptcJ70Ax cLUw== X-Gm-Message-State: AOJu0Yy3tv5rLKHt6AMyAkNLOrQTZteWZbOvZLcwSAJh0rQu9s5vlePO kR7EAFeWc15c+vAxIj7RkPY0ylaQMoLc3kZ5TcRHdq7187PsWu/9puNAk0/Fd1mz6CXICVMco/t iah4ItGeAe9Y0zNRab2yv3/el1UYx5KBlUNfGqWmvhKiZb2MLFodJvOzt8fIqXEYf0Vau9o3sfo xfh3mMCwP6q5ti75LmRuIWjTYu4awJ X-Received: by 2002:adf:fc89:0:b0:34d:118f:21ee with SMTP id ffacd0b85a97d-3504a6376bfmr15520901f8f.28.1715874084214; Thu, 16 May 2024 08:41:24 -0700 (PDT) X-Google-Smtp-Source: AGHT+IF+tepnomVDUb9QyzvQ8M2AE5U84lWKVzKXMatanQSzXVdxoWcNdh24FljrjoakoxJMxpoH1g== X-Received: by 2002:adf:fc89:0:b0:34d:118f:21ee with SMTP id ffacd0b85a97d-3504a6376bfmr15520866f8f.28.1715874083484; Thu, 16 May 2024 08:41:23 -0700 (PDT) Received: from positronik4lide.redhat.com ([87.122.57.114]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3502bbbbf9esm19177266f8f.91.2024.05.16.08.41.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 16 May 2024 08:41:23 -0700 (PDT) From: jmeng@redhat.com To: dev@openvswitch.org, i.maximets@ovn.org Date: Thu, 16 May 2024 17:41:12 +0200 Message-Id: <20240516154117.2306789-2-jmeng@redhat.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240516154117.2306789-1-jmeng@redhat.com> References: <20240516154117.2306789-1-jmeng@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v12 1/6] Add global option for JSON output to ovs-appctl. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Jakob Meng For monitoring systems such as Prometheus it would be beneficial if OVS would expose statistics in a machine-readable format. This patch introduces support for different output formats to ovs-appctl. It gains a global option '-f,--format' which changes it to print a JSON document instead of plain-text for humans. For example, a later patch implements support for 'ovs-appctl --format json dpif/show'. By default, the output format is plain-text as before. A new 'set-options' command has been added to lib/unixctl.c which allows to change the output format of the commands executed afterwards on the same socket connection. It is supposed to be run by ovs-appctl transparently for the user when a specific output format has been requested. For example, when a user calls 'ovs-appctl --format json dpif/show', then ovs-appctl will call 'set-options' to set the output format as requested by the user and afterwards it will call the actual command 'dpif/show'. This ovs-appctl behaviour has been implemented in a backward compatible way. One can use an updated client (ovs-appctl) with an old server (ovs-vswitchd) and vice versa. Of course, JSON output only works when both sides have been updated. Two access functions unixctl_command_{get,set}_output_format() and a unixctl_command_reply_json function have been added to lib/unixctl.h: unixctl_command_get_output_format() is supposed to be used in commands like 'dpif/show' to query the requested output format. When JSON output has been selected, the unixctl_command_reply_json() function can be used to return JSON objects to the client (ovs-appctl) instead of plain-text with the unixctl_command_reply{,_error}() functions. When JSON has been requested but a command has not implemented JSON output the plain-text output will be wrapped in a provisional JSON document with the following structure: {"reply":"$PLAIN_TEXT_HERE","reply-format":"plain"} Thus commands which have been executed successfully will not fail when they try to render the output at a later stage. A test for the 'version' command has been implemented which shows how the provisional JSON document looks like in practice. For a cleaner JSON document, the trailing newline has been moved from the program version string to function ovs_print_version(). This way, the plain-text output of the 'version' command has not changed. Output formatting has been moved from unixctl_client_transact() in lib/unixctl.c to utilities/ovs-appctl.c. The former merely returns the JSON objects returned from the server and the latter is now responsible for printing it properly. In popular tools like kubectl the option for output control is usually called '-o|--output' instead of '-f,--format'. But ovs-appctl already has an short option '-o' which prints the available ovs-appctl options ('--option'). The now choosen name also better alignes with ovsdb-client where '-f,--format' controls output formatting. Reported-at: https://bugzilla.redhat.com/1824861 Signed-off-by: Jakob Meng --- Documentation/ref/ovs-appctl.8.rst | 12 ++ NEWS | 3 + lib/unixctl.c | 195 ++++++++++++++++++++++------- lib/unixctl.h | 20 ++- lib/util.c | 6 +- python/ovs/unixctl/server.py | 3 - tests/appctl.py | 5 + tests/ovs-vswitchd.at | 11 ++ utilities/ovs-appctl.c | 132 ++++++++++++++++--- 9 files changed, 316 insertions(+), 71 deletions(-) diff --git a/Documentation/ref/ovs-appctl.8.rst b/Documentation/ref/ovs-appctl.8.rst index 3ce02e984..9619c1226 100644 --- a/Documentation/ref/ovs-appctl.8.rst +++ b/Documentation/ref/ovs-appctl.8.rst @@ -8,6 +8,7 @@ Synopsis ``ovs-appctl`` [``--target=`` | ``-t`` ] [``--timeout=`` | ``-T`` ] +[``--format=`` | ``-f`` ] [...] ``ovs-appctl --help`` @@ -67,6 +68,17 @@ In normal use only a single option is accepted: runtime to approximately seconds. If the timeout expires, ``ovs-appctl`` exits with a ``SIGALRM`` signal. +* ``-f `` or ``--format=`` + + Tells ``ovs-appctl`` which output format to use. By default, or with a + of ``text``, ``ovs-appctl`` will print plain-text for humans. + When is ``json``, ``ovs-appctl`` will return a JSON document. + When ``json`` is requested, but a command has not implemented JSON + output, the plain-text output will be wrapped in a provisional JSON + document with the following structure: + + ``{"reply-format":"plain","reply":"$PLAIN_TEXT_HERE"}`` + Common Commands =============== diff --git a/NEWS b/NEWS index b92cec532..3c52e5ec1 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,8 @@ Post-v3.3.0 -------------------- + - ovs-appctl: + * Added new option [-f|--format] to choose the output format, e.g. 'json' + or 'text' (by default). - Userspace datapath: * Conntrack now supports 'random' flag for selecting ports in a range while natting and 'persistent' flag for selection of the IP address diff --git a/lib/unixctl.c b/lib/unixctl.c index 103357ee9..c430eac0b 100644 --- a/lib/unixctl.c +++ b/lib/unixctl.c @@ -17,11 +17,13 @@ #include #include "unixctl.h" #include +#include #include +#include "command-line.h" #include "coverage.h" #include "dirs.h" #include "openvswitch/dynamic-string.h" -#include "openvswitch/json.h" +#include "json.h" #include "jsonrpc.h" #include "openvswitch/list.h" #include "openvswitch/poll-loop.h" @@ -50,6 +52,8 @@ struct unixctl_conn { /* Only one request can be in progress at a time. While the request is * being processed, 'request_id' is populated, otherwise it is null. */ struct json *request_id; /* ID of the currently active request. */ + + enum unixctl_output_fmt fmt; /* Output format of current connection. */ }; /* Server for control connection. */ @@ -63,6 +67,42 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); static struct shash commands = SHASH_INITIALIZER(&commands); +const char * +unixctl_output_fmt_to_string(enum unixctl_output_fmt fmt) +{ + switch (fmt) { + case UNIXCTL_OUTPUT_FMT_TEXT: + return "text"; + + case UNIXCTL_OUTPUT_FMT_JSON: + return "json"; + + default: + return NULL; + } +} + +struct json * +unixctl_output_fmt_to_json(enum unixctl_output_fmt fmt) +{ + const char *string = unixctl_output_fmt_to_string(fmt); + return string ? json_string_create(string) : NULL; +} + +bool +unixctl_output_fmt_from_string(const char *string, + enum unixctl_output_fmt *fmt) +{ + if (!strcmp(string, "text")) { + *fmt = UNIXCTL_OUTPUT_FMT_TEXT; + } else if (!strcmp(string, "json")) { + *fmt = UNIXCTL_OUTPUT_FMT_JSON; + } else { + return false; + } + return true; +} + static void unixctl_list_commands(struct unixctl_conn *conn, int argc OVS_UNUSED, const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) @@ -94,6 +134,48 @@ unixctl_version(struct unixctl_conn *conn, int argc OVS_UNUSED, unixctl_command_reply(conn, ovs_get_program_version()); } +static void +unixctl_set_options(struct unixctl_conn *conn, int argc, const char *argv[], + void *aux OVS_UNUSED) +{ + struct ovs_cmdl_parsed_option *parsed_options = NULL; + size_t n_parsed_options; + char *error = NULL; + + static const struct option options[] = { + {"format", required_argument, NULL, 'f'}, + {NULL, 0, NULL, 0}, + }; + + error = ovs_cmdl_parse_all(argc--, (char **) (argv++), options, + &parsed_options, &n_parsed_options); + if (error) { + goto error; + } + + for (size_t i = 0; i < n_parsed_options; i++) { + struct ovs_cmdl_parsed_option *parsed_option = parsed_options + i; + if (parsed_option->o == (options + 0)) { + /* format */ + if (!unixctl_output_fmt_from_string(parsed_option->arg, + &conn->fmt)) { + error = xasprintf("option format has invalid value %s", + parsed_option->arg); + goto error; + } + } else { + OVS_NOT_REACHED(); + } + } + unixctl_command_reply(conn, NULL); + free(parsed_options); + return; +error: + unixctl_command_reply_error(conn, error); + free(error); + free(parsed_options); +} + /* Registers a unixctl command with the given 'name'. 'usage' describes the * arguments to the command; it is used only for presentation to the user in * "list-commands" output. (If 'usage' is NULL, then the command is hidden.) @@ -128,36 +210,41 @@ unixctl_command_register(const char *name, const char *usage, shash_add(&commands, name, command); } +enum unixctl_output_fmt +unixctl_command_get_output_format(struct unixctl_conn *conn) +{ + return conn->fmt; +} + +void +unixctl_command_set_output_format(struct unixctl_conn *conn, + enum unixctl_output_fmt fmt) +{ + conn->fmt = fmt; +} + +/* Takes ownership of the 'body'. */ static void unixctl_command_reply__(struct unixctl_conn *conn, - bool success, const char *body) + bool success, struct json *body) { - struct json *body_json; struct jsonrpc_msg *reply; COVERAGE_INC(unixctl_replied); ovs_assert(conn->request_id); - if (!body) { - body = ""; - } - - if (body[0] && body[strlen(body) - 1] != '\n') { - body_json = json_string_create_nocopy(xasprintf("%s\n", body)); - } else { - body_json = json_string_create(body); - } - if (success) { - reply = jsonrpc_create_reply(body_json, conn->request_id); + reply = jsonrpc_create_reply(body, conn->request_id); } else { - reply = jsonrpc_create_error(body_json, conn->request_id); + reply = jsonrpc_create_error(body, conn->request_id); } if (VLOG_IS_DBG_ENABLED()) { char *id = json_to_string(conn->request_id, 0); + char *msg = json_to_string(body, JSSF_SORT); VLOG_DBG("replying with %s, id=%s: \"%s\"", - success ? "success" : "error", id, body); + success ? "success" : "error", id, msg); + free(msg); free(id); } @@ -169,23 +256,52 @@ unixctl_command_reply__(struct unixctl_conn *conn, } /* Replies to the active unixctl connection 'conn'. 'result' is sent to the - * client indicating the command was processed successfully. Only one call to - * unixctl_command_reply() or unixctl_command_reply_error() may be made per - * request. */ + * client indicating the command was processed successfully. 'result' should + * be plain-text; use unixctl_command_reply_json() to return a JSON document + * when JSON output has been requested. Only one call to + * unixctl_command_reply*() functions may be made per request. */ void unixctl_command_reply(struct unixctl_conn *conn, const char *result) { - unixctl_command_reply__(conn, true, result); + struct json * json_result = json_string_create(result ? result : ""); + + if (conn->fmt == UNIXCTL_OUTPUT_FMT_JSON) { + /* Wrap plain-text reply in provisional JSON document when JSON output + * has been requested. */ + struct json *json_reply = json_object_create(); + + json_object_put_string(json_reply, "reply-format", "plain"); + json_object_put(json_reply, "reply", json_result); + + json_result = json_reply; + } + + unixctl_command_reply__(conn, true, json_result); +} + +/* Replies to the active unixctl connection 'conn'. 'body' is sent to the + * client indicating the command was processed successfully. Use this function + * when JSON output has been requested; otherwise use unixctl_command_reply() + * for plain-text output. Only one call to unixctl_command_reply*() functions + * may be made per request. + * + * Takes ownership of the 'body'. */ +void +unixctl_command_reply_json(struct unixctl_conn *conn, struct json *body) +{ + ovs_assert(conn->fmt == UNIXCTL_OUTPUT_FMT_JSON); + unixctl_command_reply__(conn, true, body); } /* Replies to the active unixctl connection 'conn'. 'error' is sent to the - * client indicating an error occurred processing the command. Only one call to - * unixctl_command_reply() or unixctl_command_reply_error() may be made per - * request. */ + * client indicating an error occurred processing the command. 'error' should + * be plain-text. Only one call to unixctl_command_reply*() functions may be + * made per request. */ void unixctl_command_reply_error(struct unixctl_conn *conn, const char *error) { - unixctl_command_reply__(conn, false, error); + unixctl_command_reply__(conn, false, + json_string_create(error ? error : "")); } /* Creates a unixctl server listening on 'path', which for POSIX may be: @@ -250,6 +366,8 @@ unixctl_server_create(const char *path, struct unixctl_server **serverp) unixctl_command_register("list-commands", "", 0, 0, unixctl_list_commands, NULL); unixctl_command_register("version", "", 0, 0, unixctl_version, NULL); + unixctl_command_register("set-options", "[--format text|json]", 0, 2, + unixctl_set_options, NULL); struct unixctl_server *server = xmalloc(sizeof *server); server->listener = listener; @@ -381,6 +499,7 @@ unixctl_server_run(struct unixctl_server *server) struct unixctl_conn *conn = xzalloc(sizeof *conn); ovs_list_push_back(&server->conns, &conn->node); conn->rpc = jsonrpc_open(stream); + conn->fmt = UNIXCTL_OUTPUT_FMT_TEXT; } else if (error == EAGAIN) { break; } else { @@ -483,10 +602,10 @@ unixctl_client_create(const char *path, struct jsonrpc **client) * '*err' if not NULL. */ int unixctl_client_transact(struct jsonrpc *client, const char *command, int argc, - char *argv[], char **result, char **err) + char *argv[], struct json **result, struct json **err) { - struct jsonrpc_msg *request, *reply; struct json **json_args, *params; + struct jsonrpc_msg *request, *reply; int error, i; *result = NULL; @@ -506,25 +625,11 @@ unixctl_client_transact(struct jsonrpc *client, const char *command, int argc, return error; } - if (reply->error) { - if (reply->error->type == JSON_STRING) { - *err = xstrdup(json_string(reply->error)); - } else { - VLOG_WARN("%s: unexpected error type in JSON RPC reply: %s", - jsonrpc_get_name(client), - json_type_to_string(reply->error->type)); - error = EINVAL; - } - } else if (reply->result) { - if (reply->result->type == JSON_STRING) { - *result = xstrdup(json_string(reply->result)); - } else { - VLOG_WARN("%s: unexpected result type in JSON rpc reply: %s", - jsonrpc_get_name(client), - json_type_to_string(reply->result->type)); - error = EINVAL; - } - } + ovs_assert(!reply->result || !reply->error); + *result = reply->result; + reply->result = NULL; + *err = reply->error; + reply->error = NULL; jsonrpc_msg_destroy(reply); return error; diff --git a/lib/unixctl.h b/lib/unixctl.h index 4562dbc49..61b31cb3a 100644 --- a/lib/unixctl.h +++ b/lib/unixctl.h @@ -17,10 +17,22 @@ #ifndef UNIXCTL_H #define UNIXCTL_H 1 +#include + #ifdef __cplusplus extern "C" { #endif +struct json; +enum unixctl_output_fmt { + UNIXCTL_OUTPUT_FMT_TEXT = 1 << 0, + UNIXCTL_OUTPUT_FMT_JSON = 1 << 1 +}; + +const char *unixctl_output_fmt_to_string(enum unixctl_output_fmt); +struct json *unixctl_output_fmt_to_json(enum unixctl_output_fmt); +bool unixctl_output_fmt_from_string(const char *, enum unixctl_output_fmt *); + /* Server for Unix domain socket control connection. */ struct unixctl_server; int unixctl_server_create(const char *path, struct unixctl_server **); @@ -36,7 +48,7 @@ int unixctl_client_create(const char *path, struct jsonrpc **client); int unixctl_client_transact(struct jsonrpc *client, const char *command, int argc, char *argv[], - char **result, char **error); + struct json **result, struct json **error); /* Command registration. */ struct unixctl_conn; @@ -45,8 +57,14 @@ typedef void unixctl_cb_func(struct unixctl_conn *, void unixctl_command_register(const char *name, const char *usage, int min_args, int max_args, unixctl_cb_func *cb, void *aux); +enum unixctl_output_fmt +unixctl_command_get_output_format(struct unixctl_conn *); +void unixctl_command_set_output_format(struct unixctl_conn *, + enum unixctl_output_fmt); void unixctl_command_reply_error(struct unixctl_conn *, const char *error); void unixctl_command_reply(struct unixctl_conn *, const char *body); +void unixctl_command_reply_json(struct unixctl_conn *, + struct json *body); #ifdef __cplusplus } diff --git a/lib/util.c b/lib/util.c index 5c31d983a..87d5a4a90 100644 --- a/lib/util.c +++ b/lib/util.c @@ -619,11 +619,11 @@ ovs_set_program_name(const char *argv0, const char *version) free(program_version); if (!strcmp(version, VERSION)) { - program_version = xasprintf("%s (Open vSwitch) "VERSION"\n", + program_version = xasprintf("%s (Open vSwitch) "VERSION, program_name); } else { program_version = xasprintf("%s %s\n" - "Open vSwitch Library "VERSION"\n", + "Open vSwitch Library "VERSION, program_name, version); } } @@ -760,7 +760,7 @@ ovs_get_program_name(void) void ovs_print_version(uint8_t min_ofp, uint8_t max_ofp) { - printf("%s", program_version); + printf("%s\n", program_version); if (min_ofp || max_ofp) { printf("OpenFlow versions %#x:%#x\n", min_ofp, max_ofp); } diff --git a/python/ovs/unixctl/server.py b/python/ovs/unixctl/server.py index b9cb52fad..d24a7092c 100644 --- a/python/ovs/unixctl/server.py +++ b/python/ovs/unixctl/server.py @@ -87,9 +87,6 @@ class UnixctlConnection(object): if body is None: body = "" - if body and not body.endswith("\n"): - body += "\n" - if success: reply = Message.create_reply(body, self._request_id) else: diff --git a/tests/appctl.py b/tests/appctl.py index b85b364fa..e5cc28138 100644 --- a/tests/appctl.py +++ b/tests/appctl.py @@ -63,11 +63,16 @@ def main(): ovs.util.ovs_fatal(err_no, "%s: transaction error" % target) elif error is not None: sys.stderr.write(error) + if error and not error.endswith("\n"): + sys.stderr.write("\n") + ovs.util.ovs_error(0, "%s: server returned an error" % target) sys.exit(2) else: assert result is not None sys.stdout.write(result) + if result and not result.endswith("\n"): + sys.stdout.write("\n") if __name__ == '__main__': diff --git a/tests/ovs-vswitchd.at b/tests/ovs-vswitchd.at index 977b2eba1..1ae7fcc32 100644 --- a/tests/ovs-vswitchd.at +++ b/tests/ovs-vswitchd.at @@ -265,3 +265,14 @@ OFPT_FEATURES_REPLY: dpid:$orig_dpid OVS_VSWITCHD_STOP AT_CLEANUP + +AT_SETUP([ovs-vswitchd version]) +OVS_VSWITCHD_START + +AT_CHECK([ovs-appctl version], [0], [ignore]) +ovs_version=$(ovs-appctl version) + +AT_CHECK_UNQUOTED([ovs-appctl --format json version], [0], [dnl +{"reply":"$ovs_version","reply-format":"plain"}]) + +AT_CLEANUP diff --git a/utilities/ovs-appctl.c b/utilities/ovs-appctl.c index ba0c172e6..0e99259e8 100644 --- a/utilities/ovs-appctl.c +++ b/utilities/ovs-appctl.c @@ -26,57 +26,105 @@ #include "daemon.h" #include "dirs.h" #include "openvswitch/dynamic-string.h" +#include "json.h" #include "jsonrpc.h" #include "process.h" #include "timeval.h" +#include "svec.h" #include "unixctl.h" #include "util.h" #include "openvswitch/vlog.h" static void usage(void); -static const char *parse_command_line(int argc, char *argv[]); + +/* Parsed command line args. */ +struct cmdl_args { + enum unixctl_output_fmt format; + char *target; +}; + +static struct cmdl_args *cmdl_args_create(void); +static struct cmdl_args *parse_command_line(int argc, char *argv[]); static struct jsonrpc *connect_to_target(const char *target); +static char * reply_to_string(struct json *reply, enum unixctl_output_fmt fmt); int main(int argc, char *argv[]) { - char *cmd_result, *cmd_error; + struct svec opt_argv = SVEC_EMPTY_INITIALIZER; + struct json *cmd_result, *cmd_error; struct jsonrpc *client; + struct cmdl_args *args; char *cmd, **cmd_argv; - const char *target; + char *msg = NULL; int cmd_argc; int error; set_program_name(argv[0]); /* Parse command line and connect to target. */ - target = parse_command_line(argc, argv); - client = connect_to_target(target); + args = parse_command_line(argc, argv); + client = connect_to_target(args->target); - /* Transact request and process reply. */ + /* Transact options request (if required) and process reply. */ + if (args->format != UNIXCTL_OUTPUT_FMT_TEXT) { + svec_add(&opt_argv, "--format"); + svec_add(&opt_argv, unixctl_output_fmt_to_string(args->format)); + } + svec_terminate(&opt_argv); + + if (!svec_is_empty(&opt_argv)) { + error = unixctl_client_transact(client, "set-options", + opt_argv.n, opt_argv.names, + &cmd_result, &cmd_error); + + if (error) { + ovs_fatal(error, "%s: transaction error", args->target); + } + + if (cmd_error) { + jsonrpc_close(client); + msg = reply_to_string(cmd_error, UNIXCTL_OUTPUT_FMT_TEXT); + fputs(msg, stderr); + free(msg); + ovs_error(0, "%s: server returned an error", args->target); + exit(2); + } + + json_destroy(cmd_result); + json_destroy(cmd_error); + } + svec_destroy(&opt_argv); + + /* Transact command request and process reply. */ cmd = argv[optind++]; cmd_argc = argc - optind; cmd_argv = cmd_argc ? argv + optind : NULL; error = unixctl_client_transact(client, cmd, cmd_argc, cmd_argv, &cmd_result, &cmd_error); if (error) { - ovs_fatal(error, "%s: transaction error", target); + ovs_fatal(error, "%s: transaction error", args->target); } if (cmd_error) { jsonrpc_close(client); - fputs(cmd_error, stderr); - ovs_error(0, "%s: server returned an error", target); + msg = reply_to_string(cmd_error, UNIXCTL_OUTPUT_FMT_TEXT); + fputs(msg, stderr); + free(msg); + ovs_error(0, "%s: server returned an error", args->target); exit(2); } else if (cmd_result) { - fputs(cmd_result, stdout); + msg = reply_to_string(cmd_result, args->format); + fputs(msg, stdout); + free(msg); } else { OVS_NOT_REACHED(); } jsonrpc_close(client); - free(cmd_result); - free(cmd_error); + json_destroy(cmd_result); + json_destroy(cmd_error); + free(args); return 0; } @@ -101,13 +149,26 @@ Common commands:\n\ vlog/reopen Make the program reopen its log file\n\ Other options:\n\ --timeout=SECS wait at most SECS seconds for a response\n\ + -f, --format=FMT Output format. One of: 'json', or 'text'\n\ + (default: text)\n\ -h, --help Print this helpful information\n\ -V, --version Display ovs-appctl version information\n", program_name, program_name); exit(EXIT_SUCCESS); } -static const char * +static struct cmdl_args * +cmdl_args_create(void) +{ + struct cmdl_args *args = xmalloc(sizeof *args); + + args->format = UNIXCTL_OUTPUT_FMT_TEXT; + args->target = NULL; + + return args; +} + +static struct cmdl_args * parse_command_line(int argc, char *argv[]) { enum { @@ -117,6 +178,7 @@ parse_command_line(int argc, char *argv[]) static const struct option long_options[] = { {"target", required_argument, NULL, 't'}, {"execute", no_argument, NULL, 'e'}, + {"format", required_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {"option", no_argument, NULL, 'o'}, {"version", no_argument, NULL, 'V'}, @@ -126,11 +188,10 @@ parse_command_line(int argc, char *argv[]) }; char *short_options_ = ovs_cmdl_long_options_to_short_options(long_options); char *short_options = xasprintf("+%s", short_options_); - const char *target; - int e_options; + struct cmdl_args *args = cmdl_args_create(); unsigned int timeout = 0; + int e_options; - target = NULL; e_options = 0; for (;;) { int option; @@ -141,10 +202,10 @@ parse_command_line(int argc, char *argv[]) } switch (option) { case 't': - if (target) { + if (args->target) { ovs_fatal(0, "-t or --target may be specified only once"); } - target = optarg; + args->target = optarg; break; case 'e': @@ -157,6 +218,12 @@ parse_command_line(int argc, char *argv[]) } break; + case 'f': + if (!unixctl_output_fmt_from_string(optarg, &args->format)) { + ovs_fatal(0, "value %s on -f or --format is invalid", optarg); + } + break; + case 'h': usage(); break; @@ -194,7 +261,10 @@ parse_command_line(int argc, char *argv[]) "(use --help for help)"); } - return target ? target : "ovs-vswitchd"; + if (!args->target) { + args->target = "ovs-vswitchd"; + } + return args; } static struct jsonrpc * @@ -236,3 +306,27 @@ connect_to_target(const char *target) return client; } +/* The caller is responsible for freeing the returned string, with free(), when + * it is no longer needed. */ +static char * +reply_to_string(struct json *reply, enum unixctl_output_fmt fmt) +{ + ovs_assert(reply); + + if (fmt == UNIXCTL_OUTPUT_FMT_TEXT && reply->type == JSON_STRING) { + const char * body = json_string(reply); + + if (body[0] && body[strlen(body) - 1] != '\n') { + return xasprintf("%s\n", body); + } else { + return xstrdup(body); + } + } + + if (fmt != UNIXCTL_OUTPUT_FMT_JSON) { + ovs_error(0, "Unexpected reply type in JSON rpc reply: %s", + json_type_to_string(reply->type)); + } + + return json_to_string(reply, JSSF_SORT); +} From patchwork Thu May 16 15:41:13 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakob Meng X-Patchwork-Id: 1936051 X-Patchwork-Delegate: i.maximets@samsung.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=QvRPn5F0; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.137; helo=smtp4.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp4.osuosl.org (smtp4.osuosl.org [140.211.166.137]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VgDpr0D00z1ymw for ; Fri, 17 May 2024 01:41:39 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 0F1B44071F; Thu, 16 May 2024 15:41:35 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id nrBRSBgLjvhp; Thu, 16 May 2024 15:41:33 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=2605:bc80:3010:104::8cd3:938; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org D29C7407CB Authentication-Results: smtp4.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=QvRPn5F0 Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp4.osuosl.org (Postfix) with ESMTPS id D29C7407CB; Thu, 16 May 2024 15:41:32 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id A6848C0077; Thu, 16 May 2024 15:41:32 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136]) by lists.linuxfoundation.org (Postfix) with ESMTP id AB024C0037 for ; Thu, 16 May 2024 15:41:29 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id A756360E4F for ; Thu, 16 May 2024 15:41:29 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 6dlnerbQE1Uj for ; Thu, 16 May 2024 15:41:28 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.129.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=jmeng@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp3.osuosl.org 8054660847 Authentication-Results: smtp3.osuosl.org; dmarc=pass (p=none dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org 8054660847 Authentication-Results: smtp3.osuosl.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=QvRPn5F0 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by smtp3.osuosl.org (Postfix) with ESMTPS id 8054660847 for ; Thu, 16 May 2024 15:41:28 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1715874087; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=92H4KGSWJCnS0A9S4qrzPIYDIaTXRSlLDjYvtqsVbhI=; b=QvRPn5F0QpqNQLbUhzcmlIAegn3Ogt1Y9XQ1lnKiOzV9eh/Zpt7khms3NNQqbs1/LzMPOT aW1TlgEhAxIkxjIOprjpVO1gOiQIVMM4rLxEj0vdhx08uIOlpxr5AzbJdymUNtoynkA+KN Dl3PXWVaa4LqC03wyOwHdXmt0kHWUrU= Received: from mail-wr1-f72.google.com (mail-wr1-f72.google.com [209.85.221.72]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-301-2hBHX0GkPXi_e7DWJMzuiQ-1; Thu, 16 May 2024 11:41:25 -0400 X-MC-Unique: 2hBHX0GkPXi_e7DWJMzuiQ-1 Received: by mail-wr1-f72.google.com with SMTP id ffacd0b85a97d-351cb5b7649so1736327f8f.3 for ; Thu, 16 May 2024 08:41:25 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1715874084; x=1716478884; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=92H4KGSWJCnS0A9S4qrzPIYDIaTXRSlLDjYvtqsVbhI=; b=VahzfBlBgLCzHziiMp7l5/NgX28awWCKwrnjfkH9OYLTVX0w82Pcwb3hlHXzUrzhph Ha2am6but3kbzClEsmhKkY+KKuqCXAyGzBa1hqAjl1fbd8boXIXFAOkLG3AHwSkxemIY s4PnLP1FValXWqFDH404mWem4ZpKSEB1xeCBjNH+GHLto6eLQxnt0Oc7C3tmtPjXN2Tv D8EUBS4AB65H37/qCpUuNZXpiIgnFvLRmApCFh7BgFKPC62MaLrJZCo6M3e93DaXX0O9 6tLibcOBjP/X/q3+lSg4j0zU8257NA1mNN1EGTdRWeoMU84b3xV6LS7bVIRLmahLdO0+ 5zew== X-Gm-Message-State: AOJu0YxU6WsRhEoJxTsOGq43e0MTOQp0tCexkxKWumrodgGnQ5N3+NZC H9NVtKQUlM0M0866ajR+3NhpDA1J8I8PM38YsvP+tPGGlD2AJ9JOC7z/YeSYvpDydcqoH+YSVZy RpeXVwBBcKU+dL0Q4WeF5PAaJptuOLAgtJ+ZOkCueNCQILr9AMSofpCkjQ4fqH1rP6l5MjDkF/b FuGwV6RmFxIFTmqen6PKzybj09m0Tf X-Received: by 2002:adf:e543:0:b0:351:d78e:875e with SMTP id ffacd0b85a97d-351d78e8817mr1558966f8f.14.1715874084710; Thu, 16 May 2024 08:41:24 -0700 (PDT) X-Google-Smtp-Source: AGHT+IGyaQuq//xp8uI5ABXh8LDtb6+oD/oGyNyDpJRkDKutzK4h7AkXvgQ3wiWx68KB2Sunl9k8NQ== X-Received: by 2002:adf:e543:0:b0:351:d78e:875e with SMTP id ffacd0b85a97d-351d78e8817mr1558948f8f.14.1715874084320; Thu, 16 May 2024 08:41:24 -0700 (PDT) Received: from positronik4lide.redhat.com ([87.122.57.114]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3502bbbbf9esm19177266f8f.91.2024.05.16.08.41.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 16 May 2024 08:41:24 -0700 (PDT) From: jmeng@redhat.com To: dev@openvswitch.org, i.maximets@ovn.org Date: Thu, 16 May 2024 17:41:13 +0200 Message-Id: <20240516154117.2306789-3-jmeng@redhat.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240516154117.2306789-1-jmeng@redhat.com> References: <20240516154117.2306789-1-jmeng@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v12 2/6] python: Add option for JSON output to unixctl classes and appctl.py. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Jakob Meng This patch introduces support for different output formats to Python Unixctl* classes and appctl.py, similar to what the previous commit did for ovs-appctl. In particular, tests/appctl.py gains a global option '-f,--format' which allows users to request JSON instead of plain-text for humans. Reported-at: https://bugzilla.redhat.com/1824861 Signed-off-by: Jakob Meng --- NEWS | 3 +++ python/ovs/unixctl/client.py | 5 ++-- python/ovs/unixctl/server.py | 52 +++++++++++++++++++++++++++++++----- python/ovs/util.py | 8 ++++++ tests/appctl.py | 38 +++++++++++++++++++++----- tests/unixctl-py.at | 3 +++ 6 files changed, 93 insertions(+), 16 deletions(-) diff --git a/NEWS b/NEWS index 3c52e5ec1..7076939c5 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,9 @@ Post-v3.3.0 - ovs-appctl: * Added new option [-f|--format] to choose the output format, e.g. 'json' or 'text' (by default). + - Python: + * Added support for different output formats like 'json' to appctl.py and + Python's unixctl classes. - Userspace datapath: * Conntrack now supports 'random' flag for selecting ports in a range while natting and 'persistent' flag for selection of the IP address diff --git a/python/ovs/unixctl/client.py b/python/ovs/unixctl/client.py index 8283f99bb..8a6fcb1b9 100644 --- a/python/ovs/unixctl/client.py +++ b/python/ovs/unixctl/client.py @@ -14,6 +14,7 @@ import os +import ovs.json import ovs.jsonrpc import ovs.stream import ovs.util @@ -41,10 +42,10 @@ class UnixctlClient(object): return error, None, None if reply.error is not None: - return 0, str(reply.error), None + return 0, reply.error, None else: assert reply.result is not None - return 0, None, str(reply.result) + return 0, None, reply.result def close(self): self._conn.close() diff --git a/python/ovs/unixctl/server.py b/python/ovs/unixctl/server.py index d24a7092c..0665eb837 100644 --- a/python/ovs/unixctl/server.py +++ b/python/ovs/unixctl/server.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import argparse import copy import errno import os @@ -35,6 +36,7 @@ class UnixctlConnection(object): assert isinstance(rpc, ovs.jsonrpc.Connection) self._rpc = rpc self._request_id = None + self._fmt = ovs.util.OutputFormat.TEXT def run(self): self._rpc.run() @@ -63,10 +65,29 @@ class UnixctlConnection(object): return error def reply(self, body): - self._reply_impl(True, body) + assert body is None or isinstance(body, str) + + if body is None: + body = "" + + if self._fmt == ovs.util.OutputFormat.JSON: + body = { + "reply-format": "plain", + "reply": body + } + + return self._reply_impl_json(True, body) + + def reply_json(self, body): + self._reply_impl_json(True, body) def reply_error(self, body): - self._reply_impl(False, body) + assert body is None or isinstance(body, str) + + if body is None: + body = "" + + return self._reply_impl_json(False, body) # Called only by unixctl classes. def _close(self): @@ -78,15 +99,11 @@ class UnixctlConnection(object): if not self._rpc.get_backlog(): self._rpc.recv_wait(poller) - def _reply_impl(self, success, body): + def _reply_impl_json(self, success, body): assert isinstance(success, bool) - assert body is None or isinstance(body, str) assert self._request_id is not None - if body is None: - body = "" - if success: reply = Message.create_reply(body, self._request_id) else: @@ -133,6 +150,24 @@ def _unixctl_version(conn, unused_argv, version): conn.reply(version) +def _unixctl_set_options(conn, argv, unused_aux): + assert isinstance(conn, UnixctlConnection) + + parser = argparse.ArgumentParser() + parser.add_argument("--format", default="text", + choices=[fmt.name.lower() + for fmt in ovs.util.OutputFormat]) + + try: + args = parser.parse_args(args=argv) + except argparse.ArgumentError as e: + conn.reply_error(str(e)) + return + + conn._fmt = ovs.util.OutputFormat[args.format.upper()] + conn.reply(None) + + class UnixctlServer(object): def __init__(self, listener): assert isinstance(listener, ovs.stream.PassiveStream) @@ -207,4 +242,7 @@ class UnixctlServer(object): ovs.unixctl.command_register("version", "", 0, 0, _unixctl_version, version) + ovs.unixctl.command_register("set-options", "[--format text|json]", 0, + 2, _unixctl_set_options, None) + return 0, UnixctlServer(listener) diff --git a/python/ovs/util.py b/python/ovs/util.py index 3dba022f8..272ca683d 100644 --- a/python/ovs/util.py +++ b/python/ovs/util.py @@ -15,11 +15,19 @@ import os import os.path import sys +import enum PROGRAM_NAME = os.path.basename(sys.argv[0]) EOF = -1 +@enum.unique +# FIXME: Use @enum.verify(enum.NAMED_FLAGS) from Python 3.11 when available. +class OutputFormat(enum.IntFlag): + TEXT = 1 << 0 + JSON = 1 << 1 + + def abs_file_name(dir_, file_name): """If 'file_name' starts with '/', returns a copy of 'file_name'. Otherwise, returns an absolute path to 'file_name' considering it relative diff --git a/tests/appctl.py b/tests/appctl.py index e5cc28138..cf3ea3642 100644 --- a/tests/appctl.py +++ b/tests/appctl.py @@ -37,6 +37,18 @@ def connect_to_target(target): return client +def reply_to_string(reply, fmt=ovs.util.OutputFormat.TEXT): + if fmt == ovs.util.OutputFormat.TEXT: + body = str(reply) + + if body and not body.endswith("\n"): + body += "\n" + + return body + else: + return ovs.json.to_string(reply) + + def main(): parser = argparse.ArgumentParser(description="Python Implementation of" " ovs-appctl.") @@ -49,30 +61,42 @@ def main(): help="Arguments to the command.") parser.add_argument("-T", "--timeout", metavar="SECS", help="wait at most SECS seconds for a response") + parser.add_argument("-f", "--format", metavar="FMT", + help="Output format.", default="text", + choices=[fmt.name.lower() + for fmt in ovs.util.OutputFormat]) args = parser.parse_args() signal_alarm(int(args.timeout) if args.timeout else None) ovs.vlog.Vlog.init() target = args.target + format = ovs.util.OutputFormat[args.format.upper()] client = connect_to_target(target) + + if format != ovs.util.OutputFormat.TEXT: + err_no, error, _ = client.transact( + "set-options", ["--format", args.format]) + + if err_no: + ovs.util.ovs_fatal(err_no, "%s: transaction error" % target) + elif error is not None: + sys.stderr.write(reply_to_string(error)) + ovs.util.ovs_error(0, "%s: server returned an error" % target) + sys.exit(2) + err_no, error, result = client.transact(args.command, args.argv) client.close() if err_no: ovs.util.ovs_fatal(err_no, "%s: transaction error" % target) elif error is not None: - sys.stderr.write(error) - if error and not error.endswith("\n"): - sys.stderr.write("\n") - + sys.stderr.write(reply_to_string(error)) ovs.util.ovs_error(0, "%s: server returned an error" % target) sys.exit(2) else: assert result is not None - sys.stdout.write(result) - if result and not result.endswith("\n"): - sys.stdout.write("\n") + sys.stdout.write(reply_to_string(result, format)) if __name__ == '__main__': diff --git a/tests/unixctl-py.at b/tests/unixctl-py.at index 724006118..92f557b67 100644 --- a/tests/unixctl-py.at +++ b/tests/unixctl-py.at @@ -100,6 +100,7 @@ The available commands are: exit help log [[arg ...]] + set-options [[--format text|json]] version vlog/close vlog/list @@ -112,6 +113,8 @@ AT_CHECK([PYAPPCTL_PY -t test-unixctl.py help], [0], [expout]) AT_CHECK([ovs-vsctl --version | sed 's/ovs-vsctl/test-unixctl.py/' | head -1 > expout]) AT_CHECK([APPCTL -t test-unixctl.py version], [0], [expout]) AT_CHECK([PYAPPCTL_PY -t test-unixctl.py version], [0], [expout]) +AT_CHECK_UNQUOTED([PYAPPCTL_PY -t test-unixctl.py --format json version], [0], [dnl +{"reply":"$(cat expout)","reply-format":"plain"}]) AT_CHECK([APPCTL -t test-unixctl.py echo robot ninja], [0], [stdout]) AT_CHECK([cat stdout | sed -e "s/u'/'/g"], [0], [dnl From patchwork Thu May 16 15:41:14 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakob Meng X-Patchwork-Id: 1936052 X-Patchwork-Delegate: i.maximets@samsung.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=cA6PZ1sn; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.137; helo=smtp4.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp4.osuosl.org (smtp4.osuosl.org [140.211.166.137]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VgDpt3ttNz1ymw for ; Fri, 17 May 2024 01:41:42 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 972224169E; Thu, 16 May 2024 15:41:38 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id kY_ekNtzCdaE; Thu, 16 May 2024 15:41:37 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=2605:bc80:3010:104::8cd3:938; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 25D17416C3 Authentication-Results: smtp4.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=cA6PZ1sn Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp4.osuosl.org (Postfix) with ESMTPS id 25D17416C3; Thu, 16 May 2024 15:41:35 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 25454C0DCF; Thu, 16 May 2024 15:41:35 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp3.osuosl.org (smtp3.osuosl.org [140.211.166.136]) by lists.linuxfoundation.org (Postfix) with ESMTP id E1C0CC0077 for ; Thu, 16 May 2024 15:41:30 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id C295D60E7C for ; Thu, 16 May 2024 15:41:30 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id Ez5X4yEip3J9 for ; Thu, 16 May 2024 15:41:30 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.133.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=jmeng@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp3.osuosl.org CC02760847 Authentication-Results: smtp3.osuosl.org; dmarc=pass (p=none dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org CC02760847 Authentication-Results: smtp3.osuosl.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=cA6PZ1sn Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by smtp3.osuosl.org (Postfix) with ESMTPS id CC02760847 for ; Thu, 16 May 2024 15:41:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1715874088; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=4YusQp1ux8ZEvGXcSoQH1pxpJqOZMy2ZiwZtWAigL14=; b=cA6PZ1snCg2cqj9AZoN+4nYCQGP1tzzq1SXdlLzfzwSAkZcOg+BDifAC50NSijaXPEMhlR 5jlTZFWq8qZZyOlofk8lQV/0DgwYG66hJIQYDQWLYs23E+Yr8MhjQnUglg6ivEDdufyQgB LwtVKCAvzUHh20JGt4asTYjM54Q0qWI= Received: from mail-wr1-f69.google.com (mail-wr1-f69.google.com [209.85.221.69]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-260--W7Yn5SQOfuK_pv5Z9E-FQ-1; Thu, 16 May 2024 11:41:27 -0400 X-MC-Unique: -W7Yn5SQOfuK_pv5Z9E-FQ-1 Received: by mail-wr1-f69.google.com with SMTP id ffacd0b85a97d-34dc7b83209so4517032f8f.2 for ; Thu, 16 May 2024 08:41:26 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1715874085; x=1716478885; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=4YusQp1ux8ZEvGXcSoQH1pxpJqOZMy2ZiwZtWAigL14=; b=nDOfCURVJHfsIQZy+4YRq3gf6eJKLHYNH4NgzJ0CH6qoixpVJGlOzbS6ykLnrB96FV n+mua3omu7gvuSxMHhsUHqKMQ+G7A/GrWTfagG0BFsZu5NySdn7twDlfXh1Rv+EjcmQ8 gLRArlbT+NUtGCaHqxg9hgbc1EB6oAoWRwE01jYJX0c36TmgHMUGfQfTvmazKfQ7QrPd +pUyovqqHLzjfOuyj8S1pVyqdOD2drwzj7jGuMqdJyWDXMqeu4XOeUiJK12ZM/q7cimI fYEOUbCJoeLonZpuYSkoqgc1ymku7+MMrDE8XTPIiYkWnJ0Z4q2UgbLblDi2KHzbE3Aj SAzA== X-Gm-Message-State: AOJu0YzZ6KfdFOWIIPggK5uOojV6PdOzEo03MEZTN+F5vFjfiG/OXnxo STmaFWrmh0aqTMf8RQCFdOwnJLS6A1Kzw5v5+3Sj43EEK7obyZuYQY6P2xEMiPbRrbyxX/MrkFy Ki3GZ12ILFJ+gv9GSIHOTuZC/8SvI58pdI1DAYIbwKMsRjburqdvYzlCBWRuiJbjcLWBduGBNLQ Pp4wRqYNlGw6zOBD65o+iZYyJvjOPM X-Received: by 2002:adf:e68f:0:b0:351:d3e1:97e6 with SMTP id ffacd0b85a97d-351d3e1982dmr2713036f8f.26.1715874085423; Thu, 16 May 2024 08:41:25 -0700 (PDT) X-Google-Smtp-Source: AGHT+IF9pSswS7ZfXwDqQDGCRUYGXJ95khnSVKupDhMmL3LmgQu51DK1FzCMpL3QVbEusUmRRgW59Q== X-Received: by 2002:adf:e68f:0:b0:351:d3e1:97e6 with SMTP id ffacd0b85a97d-351d3e1982dmr2713014f8f.26.1715874084937; Thu, 16 May 2024 08:41:24 -0700 (PDT) Received: from positronik4lide.redhat.com ([87.122.57.114]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3502bbbbf9esm19177266f8f.91.2024.05.16.08.41.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 16 May 2024 08:41:24 -0700 (PDT) From: jmeng@redhat.com To: dev@openvswitch.org, i.maximets@ovn.org Date: Thu, 16 May 2024 17:41:14 +0200 Message-Id: <20240516154117.2306789-4-jmeng@redhat.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240516154117.2306789-1-jmeng@redhat.com> References: <20240516154117.2306789-1-jmeng@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v12 3/6] appctl: Add option '--pretty' for pretty-printing JSON output. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Jakob Meng With the '--pretty' option, ovs-appctl will now print JSON output in a more readable fashion, i.e. with additional line breaks, spaces and sorted dictionary keys. Signed-off-by: Jakob Meng --- Documentation/ref/ovs-appctl.8.rst | 7 ++++++ NEWS | 1 + tests/ovs-vswitchd.at | 5 +++++ utilities/ovs-appctl.c | 34 ++++++++++++++++++++++++------ 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/Documentation/ref/ovs-appctl.8.rst b/Documentation/ref/ovs-appctl.8.rst index 9619c1226..db6c52b42 100644 --- a/Documentation/ref/ovs-appctl.8.rst +++ b/Documentation/ref/ovs-appctl.8.rst @@ -79,6 +79,13 @@ In normal use only a single option is accepted: ``{"reply-format":"plain","reply":"$PLAIN_TEXT_HERE"}`` +* ``--pretty`` + + By default, JSON output is printed as compactly as possible. This option + causes JSON in output to be printed in a more readable fashion. For example, + members of objects and elements of arrays are printed one per line, with + indentation. Requires ``--format=json``. + Common Commands =============== diff --git a/NEWS b/NEWS index 7076939c5..0d41061d3 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,7 @@ Post-v3.3.0 - ovs-appctl: * Added new option [-f|--format] to choose the output format, e.g. 'json' or 'text' (by default). + * Added new option [--pretty] to print JSON output in a readable fashion. - Python: * Added support for different output formats like 'json' to appctl.py and Python's unixctl classes. diff --git a/tests/ovs-vswitchd.at b/tests/ovs-vswitchd.at index 1ae7fcc32..2db8138f1 100644 --- a/tests/ovs-vswitchd.at +++ b/tests/ovs-vswitchd.at @@ -275,4 +275,9 @@ ovs_version=$(ovs-appctl version) AT_CHECK_UNQUOTED([ovs-appctl --format json version], [0], [dnl {"reply":"$ovs_version","reply-format":"plain"}]) +AT_CHECK_UNQUOTED([ovs-appctl --format json --pretty version], [0], [dnl +{ + "reply": "$ovs_version", + "reply-format": "plain"}]) + AT_CLEANUP diff --git a/utilities/ovs-appctl.c b/utilities/ovs-appctl.c index 0e99259e8..3b76740ea 100644 --- a/utilities/ovs-appctl.c +++ b/utilities/ovs-appctl.c @@ -40,13 +40,15 @@ static void usage(void); /* Parsed command line args. */ struct cmdl_args { enum unixctl_output_fmt format; + unsigned int format_flags; char *target; }; static struct cmdl_args *cmdl_args_create(void); static struct cmdl_args *parse_command_line(int argc, char *argv[]); static struct jsonrpc *connect_to_target(const char *target); -static char * reply_to_string(struct json *reply, enum unixctl_output_fmt fmt); +static char * reply_to_string(struct json *reply, enum unixctl_output_fmt fmt, + unsigned int fmt_flags); int main(int argc, char *argv[]) @@ -84,7 +86,7 @@ main(int argc, char *argv[]) if (cmd_error) { jsonrpc_close(client); - msg = reply_to_string(cmd_error, UNIXCTL_OUTPUT_FMT_TEXT); + msg = reply_to_string(cmd_error, UNIXCTL_OUTPUT_FMT_TEXT, 0); fputs(msg, stderr); free(msg); ovs_error(0, "%s: server returned an error", args->target); @@ -108,13 +110,13 @@ main(int argc, char *argv[]) if (cmd_error) { jsonrpc_close(client); - msg = reply_to_string(cmd_error, UNIXCTL_OUTPUT_FMT_TEXT); + msg = reply_to_string(cmd_error, UNIXCTL_OUTPUT_FMT_TEXT, 0); fputs(msg, stderr); free(msg); ovs_error(0, "%s: server returned an error", args->target); exit(2); } else if (cmd_result) { - msg = reply_to_string(cmd_result, args->format); + msg = reply_to_string(cmd_result, args->format, args->format_flags); fputs(msg, stdout); free(msg); } else { @@ -151,6 +153,8 @@ Other options:\n\ --timeout=SECS wait at most SECS seconds for a response\n\ -f, --format=FMT Output format. One of: 'json', or 'text'\n\ (default: text)\n\ + --pretty Format the output in a more readable fashion.\n\ + Requires: --format=json.\n\ -h, --help Print this helpful information\n\ -V, --version Display ovs-appctl version information\n", program_name, program_name); @@ -163,6 +167,7 @@ cmdl_args_create(void) struct cmdl_args *args = xmalloc(sizeof *args); args->format = UNIXCTL_OUTPUT_FMT_TEXT; + args->format_flags = 0; args->target = NULL; return args; @@ -173,7 +178,8 @@ parse_command_line(int argc, char *argv[]) { enum { OPT_START = UCHAR_MAX + 1, - VLOG_OPTION_ENUMS + OPT_PRETTY, + VLOG_OPTION_ENUMS, }; static const struct option long_options[] = { {"target", required_argument, NULL, 't'}, @@ -181,6 +187,7 @@ parse_command_line(int argc, char *argv[]) {"format", required_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {"option", no_argument, NULL, 'o'}, + {"pretty", no_argument, NULL, OPT_PRETTY}, {"version", no_argument, NULL, 'V'}, {"timeout", required_argument, NULL, 'T'}, VLOG_LONG_OPTIONS, @@ -190,6 +197,7 @@ parse_command_line(int argc, char *argv[]) char *short_options = xasprintf("+%s", short_options_); struct cmdl_args *args = cmdl_args_create(); unsigned int timeout = 0; + bool pretty = false; int e_options; e_options = 0; @@ -232,6 +240,10 @@ parse_command_line(int argc, char *argv[]) ovs_cmdl_print_options(long_options); exit(EXIT_SUCCESS); + case OPT_PRETTY: + pretty = true; + break; + case 'T': if (!str_to_uint(optarg, 10, &timeout) || !timeout) { ovs_fatal(0, "value %s on -T or --timeout is invalid", optarg); @@ -261,6 +273,13 @@ parse_command_line(int argc, char *argv[]) "(use --help for help)"); } + if (pretty) { + if (args->format != UNIXCTL_OUTPUT_FMT_JSON) { + ovs_fatal(0, "--pretty is supported with --format json only"); + } + args->format_flags |= JSSF_PRETTY; + } + if (!args->target) { args->target = "ovs-vswitchd"; } @@ -309,7 +328,8 @@ connect_to_target(const char *target) /* The caller is responsible for freeing the returned string, with free(), when * it is no longer needed. */ static char * -reply_to_string(struct json *reply, enum unixctl_output_fmt fmt) +reply_to_string(struct json *reply, enum unixctl_output_fmt fmt, + unsigned int fmt_flags) { ovs_assert(reply); @@ -328,5 +348,5 @@ reply_to_string(struct json *reply, enum unixctl_output_fmt fmt) json_type_to_string(reply->type)); } - return json_to_string(reply, JSSF_SORT); + return json_to_string(reply, JSSF_SORT | fmt_flags); } From patchwork Thu May 16 15:41:15 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakob Meng X-Patchwork-Id: 1936054 X-Patchwork-Delegate: i.maximets@samsung.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=EiJv4bCw; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VgDpx4R8fz1ymw for ; Fri, 17 May 2024 01:41:45 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 233AE83A4A; Thu, 16 May 2024 15:41:43 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 3KFXg__GxJu0; Thu, 16 May 2024 15:41:40 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=140.211.9.56; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp1.osuosl.org F3DFD838DE Authentication-Results: smtp1.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=EiJv4bCw Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp1.osuosl.org (Postfix) with ESMTPS id F3DFD838DE; Thu, 16 May 2024 15:41:36 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 2BEE0C0DD8; Thu, 16 May 2024 15:41:36 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) by lists.linuxfoundation.org (Postfix) with ESMTP id 07014C007C for ; Thu, 16 May 2024 15:41:31 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id E0553836F6 for ; Thu, 16 May 2024 15:41:30 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id PbnVIDNDqFwT for ; Thu, 16 May 2024 15:41:30 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.129.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=jmeng@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp1.osuosl.org D02BC818D4 Authentication-Results: smtp1.osuosl.org; dmarc=pass (p=none dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp1.osuosl.org D02BC818D4 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by smtp1.osuosl.org (Postfix) with ESMTPS id D02BC818D4 for ; Thu, 16 May 2024 15:41:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1715874088; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=aj7h841kSax5+zyjtfQL96Z7hWMdn3+0BPP8WXM04Ms=; b=EiJv4bCw+lWt6FJqppn4oWLyBuV6+vmvHokLqddPVG6PeDrfbwUocpGjSwfp8s8mxTNBdD H3id4q/06yo3oZMwWcTW1yMJ34RiyNwqr5b5GN7TS2lJEbRAwp/abMWZvtrqyvXxbpsThC tihIOji1Qrnxq+pJJyC0yesNQHmX5K4= Received: from mail-wm1-f72.google.com (mail-wm1-f72.google.com [209.85.128.72]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-153-D7EqAsbdNlmYU0ZKz8v7Qg-1; Thu, 16 May 2024 11:41:27 -0400 X-MC-Unique: D7EqAsbdNlmYU0ZKz8v7Qg-1 Received: by mail-wm1-f72.google.com with SMTP id 5b1f17b1804b1-4200ebdbf55so28770705e9.1 for ; Thu, 16 May 2024 08:41:26 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1715874086; x=1716478886; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=aj7h841kSax5+zyjtfQL96Z7hWMdn3+0BPP8WXM04Ms=; b=cNcLC3MSnBbnV/RSADSUDZ/aEK/fWMM/U3yWHSoz2gF18Z/TM+gGx13R7PkZL+4Nvb IHnXn79HuL5unKdgIoym6UUNdGNi2gwA8fuHpegju7EJkUabkNufXdOUvrJIoh7zgWkV 1BAP2PXuHGa4ap30f5dKI2dhaBx69Gw2H4ndfp/7NIt0lZrPkm+dgGXajXfNoqkTmFaR ExEpPrZdJM2j+HmxRDw2oqwpHQvINAzkiXRQ03B1bNhKbsWW9vg0iT9rUK8uhOvU0vxe Az6xp6FebQcvXG5gbGgtIg3uQZRawYVbUgQAv3weC9/JhUjiGgv1dCnLs84pqrBNqn+T Ydkw== X-Gm-Message-State: AOJu0YwgQqlMaWrqmQZaO6L4xSgQ7IZmO6wTXXY4R3cYrfLvyubFnTmi 8bvVwuRR1gaAmoo+sxdATD6Q0dMKTImtheNqkli5xHaTAt8mqERBA9zYubN9xebpBQyrYh2YjN1 cV/N2MKHm6z5If5yYKdl7hdyHDJkMxMFho72NfuUMWfXQxzbMmmDO7A0txWRCv1jv/OwCqTy6QH 4P2DK93fTT1gT0i1ZeB0oy6J3m4tok X-Received: by 2002:a5d:5b86:0:b0:34d:85e4:7639 with SMTP id ffacd0b85a97d-3504a737d26mr10466036f8f.35.1715874085956; Thu, 16 May 2024 08:41:25 -0700 (PDT) X-Google-Smtp-Source: AGHT+IHAK3dfWTq7w7/b59DbfgILBsj2Ns29KN2vIdmpsJPRHRk1N81zuRHU3iHsdXDb+PjW8IeapQ== X-Received: by 2002:a5d:5b86:0:b0:34d:85e4:7639 with SMTP id ffacd0b85a97d-3504a737d26mr10466027f8f.35.1715874085637; Thu, 16 May 2024 08:41:25 -0700 (PDT) Received: from positronik4lide.redhat.com ([87.122.57.114]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3502bbbbf9esm19177266f8f.91.2024.05.16.08.41.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 16 May 2024 08:41:25 -0700 (PDT) From: jmeng@redhat.com To: dev@openvswitch.org, i.maximets@ovn.org Date: Thu, 16 May 2024 17:41:15 +0200 Message-Id: <20240516154117.2306789-5-jmeng@redhat.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240516154117.2306789-1-jmeng@redhat.com> References: <20240516154117.2306789-1-jmeng@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v12 4/6] python: Add option for pretty-printing JSON output to appctl.py. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Jakob Meng With the '--pretty' option, appctl.py will now print JSON output in a more readable fashion, i.e. with additional line breaks, spaces and sorted dictionary keys. The pretty-printed output from appctl.py is not strictly the same as with ovs-appctl because of both use different pretty-printing implementations. Signed-off-by: Jakob Meng --- tests/appctl.py | 13 ++++++++++--- tests/unixctl-py.at | 5 +++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/appctl.py b/tests/appctl.py index cf3ea3642..b08bf9033 100644 --- a/tests/appctl.py +++ b/tests/appctl.py @@ -37,7 +37,7 @@ def connect_to_target(target): return client -def reply_to_string(reply, fmt=ovs.util.OutputFormat.TEXT): +def reply_to_string(reply, fmt=ovs.util.OutputFormat.TEXT, fmt_flags={}): if fmt == ovs.util.OutputFormat.TEXT: body = str(reply) @@ -46,7 +46,7 @@ def reply_to_string(reply, fmt=ovs.util.OutputFormat.TEXT): return body else: - return ovs.json.to_string(reply) + return ovs.json.to_string(reply, **fmt_flags) def main(): @@ -65,13 +65,20 @@ def main(): help="Output format.", default="text", choices=[fmt.name.lower() for fmt in ovs.util.OutputFormat]) + parser.add_argument("--pretty", action="store_true", + help="Format the output in a more readable fashion." + " Requires: --format json.") args = parser.parse_args() + if args.format != ovs.util.OutputFormat.JSON.name.lower() and args.pretty: + ovs.util.ovs_fatal(0, "--pretty is supported with --format json only") + signal_alarm(int(args.timeout) if args.timeout else None) ovs.vlog.Vlog.init() target = args.target format = ovs.util.OutputFormat[args.format.upper()] + format_flags = dict(pretty=True) if args.pretty else {} client = connect_to_target(target) if format != ovs.util.OutputFormat.TEXT: @@ -96,7 +103,7 @@ def main(): sys.exit(2) else: assert result is not None - sys.stdout.write(reply_to_string(result, format)) + sys.stdout.write(reply_to_string(result, format, format_flags)) if __name__ == '__main__': diff --git a/tests/unixctl-py.at b/tests/unixctl-py.at index 92f557b67..dffe40d2b 100644 --- a/tests/unixctl-py.at +++ b/tests/unixctl-py.at @@ -115,6 +115,11 @@ AT_CHECK([APPCTL -t test-unixctl.py version], [0], [expout]) AT_CHECK([PYAPPCTL_PY -t test-unixctl.py version], [0], [expout]) AT_CHECK_UNQUOTED([PYAPPCTL_PY -t test-unixctl.py --format json version], [0], [dnl {"reply":"$(cat expout)","reply-format":"plain"}]) +AT_CHECK_UNQUOTED([PYAPPCTL_PY -t test-unixctl.py --format json --pretty version], [0], [dnl +{ + "reply":"$(cat expout)", + "reply-format":"plain" +}]) AT_CHECK([APPCTL -t test-unixctl.py echo robot ninja], [0], [stdout]) AT_CHECK([cat stdout | sed -e "s/u'/'/g"], [0], [dnl From patchwork Thu May 16 15:41:16 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakob Meng X-Patchwork-Id: 1936055 X-Patchwork-Delegate: i.maximets@samsung.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=habO/jRL; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VgDq03VSRz1ymw for ; Fri, 17 May 2024 01:41:48 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id A99D883A67; Thu, 16 May 2024 15:41:45 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 5qqL7WZpF1Yz; Thu, 16 May 2024 15:41:42 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=2605:bc80:3010:104::8cd3:938; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp1.osuosl.org 6CE02837B9 Authentication-Results: smtp1.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=habO/jRL Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp1.osuosl.org (Postfix) with ESMTPS id 6CE02837B9; Thu, 16 May 2024 15:41:38 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 12EEDC0DD9; Thu, 16 May 2024 15:41:38 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::133]) by lists.linuxfoundation.org (Postfix) with ESMTP id 87F43C0DCF for ; Thu, 16 May 2024 15:41:33 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id 71A3F41D2E for ; Thu, 16 May 2024 15:41:33 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp2.osuosl.org ([127.0.0.1]) by localhost (smtp2.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id m4JEF9rFEx0j for ; Thu, 16 May 2024 15:41:32 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.133.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=jmeng@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp2.osuosl.org 35A2441D23 Authentication-Results: smtp2.osuosl.org; dmarc=pass (p=none dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp2.osuosl.org 35A2441D23 Authentication-Results: smtp2.osuosl.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=habO/jRL Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by smtp2.osuosl.org (Postfix) with ESMTPS id 35A2441D23 for ; Thu, 16 May 2024 15:41:32 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1715874091; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=DFnaZj3pXR/HFD/yNtUHiNlrmVlrWAUDyqZ6SotuayU=; b=habO/jRLxFBDexmc6tzHzHonMzEomXQvCnfz4PMclHZRX8U/Vvapl68Ppgd7xk2tU7y02u uipccd60SvJaVFgT4F9dVcC3e4TAyyNAo8fVH3RmQ+1qLwMrkaJ7oQwgBIAon+hwTcRktA /UnleKCmIf1aZ7/iSe5wARgrVa7r/Hg= Received: from mail-lf1-f72.google.com (mail-lf1-f72.google.com [209.85.167.72]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-544-oo-rIzDfNFKonDdllfp_sA-1; Thu, 16 May 2024 11:41:30 -0400 X-MC-Unique: oo-rIzDfNFKonDdllfp_sA-1 Received: by mail-lf1-f72.google.com with SMTP id 2adb3069b0e04-51f6fb04b82so7296861e87.2 for ; Thu, 16 May 2024 08:41:29 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1715874088; x=1716478888; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=DFnaZj3pXR/HFD/yNtUHiNlrmVlrWAUDyqZ6SotuayU=; b=vY75utwygzGUWxqZb5fTBIYB+cJwdLQKK1Ctbdy6bOdLxhcm2f8qDETOcFphRFozlG 3NlnK0acjTUoCmoRfa8Wzx1PPorVgyWH08c6MXXHneOytqV1UKccrtXnR9D6RpoavN1u uUxRtjtLlKNOG6N8PDslqUw0fwT37hJ1GL4shDGgw+HZ9iu7/XHNNj4uaAjN31QGneHc 0X9DEVxexGqbWhAA6EP9tzWkq/uy/OkwmYvTmXajL6I81PfY5hMFs3dSrQs1ukgxz8Zx OIsK/iey+l3BOPJxiG7clo0WBhSN1tF/ATGE+t/17esxmkzAoMwZ8na6z/nvrF/t+FQi 6cyg== X-Gm-Message-State: AOJu0YyXEyp48eMzcDJg1rVjzQAZZ849vn0ZbmlTf1BzQu5b8ZhxcVMM D6J3ZPvUlK6dgxnbSP9OEzPDbOCeedatDrvLmiTN9oZOLS7eKn+5J/taN14Ze+IPTZwpfydxs8j 3TOyiw0+4VN5eGI/JVBmqdVUKTZdYebiZMShnf9TWrlOasZjH3y+/KdoUZvQRa8NbNMyoe5Q0nU asKFiKYtCkS2oFDjuTOI21nLE6eNty X-Received: by 2002:ac2:424d:0:b0:521:b2b5:60ea with SMTP id 2adb3069b0e04-5220fb6b131mr12344012e87.23.1715874088622; Thu, 16 May 2024 08:41:28 -0700 (PDT) X-Google-Smtp-Source: AGHT+IFXPbJbJ1lm7qBVHXy9bB2C3ITZXXkrpwxB60h+5NZhLm01ayLLhsWiieUWXnBpl7gu3PB+Nw== X-Received: by 2002:ac2:424d:0:b0:521:b2b5:60ea with SMTP id 2adb3069b0e04-5220fb6b131mr12343940e87.23.1715874086289; Thu, 16 May 2024 08:41:26 -0700 (PDT) Received: from positronik4lide.redhat.com ([87.122.57.114]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3502bbbbf9esm19177266f8f.91.2024.05.16.08.41.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 16 May 2024 08:41:26 -0700 (PDT) From: jmeng@redhat.com To: dev@openvswitch.org, i.maximets@ovn.org Date: Thu, 16 May 2024 17:41:16 +0200 Message-Id: <20240516154117.2306789-6-jmeng@redhat.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240516154117.2306789-1-jmeng@redhat.com> References: <20240516154117.2306789-1-jmeng@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v12 5/6] vswitchd: Add JSON output for 'list-commands' command. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Jakob Meng The 'list-commands' command now supports machine-readable JSON output in addition to the plain-text output for humans. Reported-at: https://bugzilla.redhat.com/1824861 Signed-off-by: Jakob Meng --- NEWS | 1 + lib/unixctl.c | 44 +++++++++++++++++++++++++++++-------------- tests/ovs-vswitchd.at | 13 +++++++++++++ 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/NEWS b/NEWS index 0d41061d3..479310d49 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ Post-v3.3.0 * Added new option [-f|--format] to choose the output format, e.g. 'json' or 'text' (by default). * Added new option [--pretty] to print JSON output in a readable fashion. + * 'list-commands' now supports output in JSON format. - Python: * Added support for different output formats like 'json' to appctl.py and Python's unixctl classes. diff --git a/lib/unixctl.c b/lib/unixctl.c index c430eac0b..c84ca125f 100644 --- a/lib/unixctl.c +++ b/lib/unixctl.c @@ -107,24 +107,40 @@ static void unixctl_list_commands(struct unixctl_conn *conn, int argc OVS_UNUSED, const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) { - struct ds ds = DS_EMPTY_INITIALIZER; - const struct shash_node **nodes = shash_sort(&commands); - size_t i; + if (unixctl_command_get_output_format(conn) == UNIXCTL_OUTPUT_FMT_JSON) { + struct json *json_commands = json_object_create(); + const struct shash_node *node; - ds_put_cstr(&ds, "The available commands are:\n"); + SHASH_FOR_EACH (node, &commands) { + const struct unixctl_command *command = node->data; - for (i = 0; i < shash_count(&commands); i++) { - const struct shash_node *node = nodes[i]; - const struct unixctl_command *command = node->data; - - if (command->usage) { - ds_put_format(&ds, " %-23s %s\n", node->name, command->usage); + if (command->usage) { + json_object_put_string(json_commands, node->name, + command->usage); + } } - } - free(nodes); + unixctl_command_reply_json(conn, json_commands); + } else { + struct ds ds = DS_EMPTY_INITIALIZER; + const struct shash_node **nodes = shash_sort(&commands); + size_t i; - unixctl_command_reply(conn, ds_cstr(&ds)); - ds_destroy(&ds); + ds_put_cstr(&ds, "The available commands are:\n"); + + for (i = 0; i < shash_count(&commands); ++i) { + const struct shash_node *node = nodes[i]; + const struct unixctl_command *command = node->data; + + if (command->usage) { + ds_put_format(&ds, " %-23s %s\n", node->name, + command->usage); + } + } + free(nodes); + + unixctl_command_reply(conn, ds_cstr(&ds)); + ds_destroy(&ds); + } } static void diff --git a/tests/ovs-vswitchd.at b/tests/ovs-vswitchd.at index 2db8138f1..4f909603a 100644 --- a/tests/ovs-vswitchd.at +++ b/tests/ovs-vswitchd.at @@ -281,3 +281,16 @@ AT_CHECK_UNQUOTED([ovs-appctl --format json --pretty version], [0], [dnl "reply-format": "plain"}]) AT_CLEANUP + +AT_SETUP([ovs-vswitchd list-commands]) +OVS_VSWITCHD_START + +AT_CHECK([ovs-appctl list-commands], [0], [ignore]) +AT_CHECK([ovs-appctl --format json list-commands], [0], [stdout]) +# Check that ovs-appctl prints a single line without a trailing newline. +AT_CHECK([wc -l stdout], [0], [0 stdout +]) +# Check that ovs-appctl prints text which roughly looks like a JSON object. +AT_CHECK([grep -E '^\{"autoattach/show-isid":.*\}$' stdout], [0], [ignore]) + +AT_CLEANUP From patchwork Thu May 16 15:41:17 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakob Meng X-Patchwork-Id: 1936056 X-Patchwork-Delegate: i.maximets@samsung.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=YFlAgWac; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VgDq44Rf7z1ymw for ; Fri, 17 May 2024 01:41:52 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id A3D0E83A9A; Thu, 16 May 2024 15:41:49 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id gStt8ZNcFfJ7; Thu, 16 May 2024 15:41:45 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=2605:bc80:3010:104::8cd3:938; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp1.osuosl.org 68B7D839F9 Authentication-Results: smtp1.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=YFlAgWac Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp1.osuosl.org (Postfix) with ESMTPS id 68B7D839F9; Thu, 16 May 2024 15:41:40 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 0301AC0DD4; Thu, 16 May 2024 15:41:40 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp3.osuosl.org (smtp3.osuosl.org [140.211.166.136]) by lists.linuxfoundation.org (Postfix) with ESMTP id CD71DC0037 for ; Thu, 16 May 2024 15:41:34 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id C1A1960E84 for ; Thu, 16 May 2024 15:41:34 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id m-Q8ji77EG6Q for ; Thu, 16 May 2024 15:41:33 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.129.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=jmeng@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp3.osuosl.org 6821C60E89 Authentication-Results: smtp3.osuosl.org; dmarc=pass (p=none dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org 6821C60E89 Authentication-Results: smtp3.osuosl.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=YFlAgWac Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by smtp3.osuosl.org (Postfix) with ESMTPS id 6821C60E89 for ; Thu, 16 May 2024 15:41:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1715874092; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=/9SVtKmKmN8Px7Z7JPWZXF8mRbAkTSExvXxmLgIqmFQ=; b=YFlAgWacVr9EABdb2E2A2W4Wx+JEnU1Sn7HcWrkWkq27/WLtQKVAkYoTQe9fN6Rz5yKeky 1qRkC8KQ2QWLcZsgZMOho5mFwsnD0HZFn8k59qLVKh/wjnUEHZp1C/9uXJFYlnaBUfjFy7 rFs5gsMSfMwLLt5IKTS+vriZGqXPf3Q= Received: from mail-lf1-f72.google.com (mail-lf1-f72.google.com [209.85.167.72]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-211-HVFtosNfN1yAYC4lFn4TPw-1; Thu, 16 May 2024 11:41:30 -0400 X-MC-Unique: HVFtosNfN1yAYC4lFn4TPw-1 Received: by mail-lf1-f72.google.com with SMTP id 2adb3069b0e04-5234e83c4a6so4142587e87.0 for ; Thu, 16 May 2024 08:41:30 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1715874089; x=1716478889; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=/9SVtKmKmN8Px7Z7JPWZXF8mRbAkTSExvXxmLgIqmFQ=; b=eFowIpzR/q2e7tCHiNkh4jFgC215fLDJn0Rj+JiNO/LKI4sFLpxIelUEX3JWi8yDxp u6bwliMPp5RHACdlKL3dy6zbAyw30VTucmt1Hn7MYCiQ1r++TiwZmIzPm9zzsUuisuYy u/n8EAYDQGfyYgH+2AaTP5f8QnqoMYbZMzeuQP335qIj6LuiQblLTD3P1LJ2aEND/BpU InU1Yt3SoAuCEXGJGl65XOM+LgAFRcd3gtWWbJdjW+oi7EzkHUifC//dCd/L6zlCQrvI 0Fi2pXGVPAup8YNA8pYLgX7S7/pf85k9o8hdFOYJURGABxiXkz95DMgsQt2c4SzSCu7S RQow== X-Gm-Message-State: AOJu0YwMgsh40UCMYYATakyc+CtPf+DOTERBQaTdkRIEB5dLx29Jsq3V o95qqpG+ETPUB/vBdCOwjXQIw7Wl1T3bOyCgZgKMMF/8ZuBOSQpHbYv8w5bFhSedJlWHm3hrOxJ m+/nqtBW5Uhert6toK7jDvt2HOJKn10mSC0FTnBx5+cLjR609d7prY3c9akA2Yw3GEYRyvBNmcl newMQQE1LBuFexLvQ4Uxqc/YekapLB X-Received: by 2002:a05:6512:688:b0:523:dab3:39 with SMTP id 2adb3069b0e04-523dab300c6mr52277e87.55.1715874089375; Thu, 16 May 2024 08:41:29 -0700 (PDT) X-Google-Smtp-Source: AGHT+IGwr+GcePYivMZcjRUHEn1GPPslimadS7C1ggmZ0cNBb1KE/Gl0kodWiUKFhgp8cutMZz88LQ== X-Received: by 2002:a05:6512:688:b0:523:dab3:39 with SMTP id 2adb3069b0e04-523dab300c6mr52260e87.55.1715874088922; Thu, 16 May 2024 08:41:28 -0700 (PDT) Received: from positronik4lide.redhat.com ([87.122.57.114]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3502bbbbf9esm19177266f8f.91.2024.05.16.08.41.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 16 May 2024 08:41:28 -0700 (PDT) From: jmeng@redhat.com To: dev@openvswitch.org, i.maximets@ovn.org Date: Thu, 16 May 2024 17:41:17 +0200 Message-Id: <20240516154117.2306789-7-jmeng@redhat.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240516154117.2306789-1-jmeng@redhat.com> References: <20240516154117.2306789-1-jmeng@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v12 6/6] ofproto: Add JSON output for 'dpif/show' command. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Jakob Meng The 'dpif/show' command now supports machine-readable JSON output in addition to the plain-text output for humans. An example would be: ovs-appctl --format json dpif/show Reported-at: https://bugzilla.redhat.com/1824861 Signed-off-by: Jakob Meng --- NEWS | 1 + ofproto/ofproto-dpif.c | 120 +++++++++++++++++++++++++++++++++++++---- tests/pmd.at | 23 ++++++++ 3 files changed, 133 insertions(+), 11 deletions(-) diff --git a/NEWS b/NEWS index 479310d49..c434b1497 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ Post-v3.3.0 or 'text' (by default). * Added new option [--pretty] to print JSON output in a readable fashion. * 'list-commands' now supports output in JSON format. + * 'dpif/show' now supports output in JSON format. - Python: * Added support for different output formats like 'json' to appctl.py and Python's unixctl classes. diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index 32d037be6..db0405886 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -28,6 +28,7 @@ #include "fail-open.h" #include "guarded-list.h" #include "hmapx.h" +#include "json.h" #include "lacp.h" #include "learn.h" #include "mac-learning.h" @@ -6519,8 +6520,99 @@ done: return changed; } +static struct json * +dpif_show_backer_json(struct json *backers, const struct dpif_backer *backer) +{ + struct json *json_backer = json_object_create(); + + /* Add datapath as new JSON object using its name as key. */ + json_object_put(backers, dpif_name(backer->dpif), json_backer); + + /* Add datapath's stats under "stats" key. */ + struct dpif_dp_stats dp_stats; + struct json *json_dp_stats = json_object_create(); + + json_object_put(json_backer, "stats", json_dp_stats); + dpif_get_dp_stats(backer->dpif, &dp_stats); + json_object_put_format(json_dp_stats, "n_hit", "%"PRIu64, dp_stats.n_hit); + json_object_put_format(json_dp_stats, "n_missed", "%"PRIu64, + dp_stats.n_missed); + + /* Add datapath's bridges under "bridges" key. */ + struct json *json_dp_bridges = json_object_create(); + json_object_put(json_backer, "bridges", json_dp_bridges); + + struct shash ofproto_shash; + shash_init(&ofproto_shash); + const struct shash_node **ofprotos = get_ofprotos(&ofproto_shash); + for (size_t i = 0; i < shash_count(&ofproto_shash); i++) { + struct ofproto_dpif *ofproto = ofprotos[i]->data; + + if (ofproto->backer != backer) { + continue; + } + + struct json *json_ofproto = json_object_create(); + /* Add bridge to "bridges" dictionary using its name as key. */ + json_object_put(json_dp_bridges, ofproto->up.name, json_ofproto); + + /* Add bridge ports to the current bridge dictionary. */ + const struct shash_node **ports; + ports = shash_sort(&ofproto->up.port_by_name); + for (size_t j = 0; j < shash_count(&ofproto->up.port_by_name); j++) { + const struct shash_node *port = ports[j]; + struct ofport *ofport = port->data; + + struct json * json_ofproto_port = json_object_create(); + /* Add bridge port to a bridge's dict using port name as key. */ + json_object_put(json_ofproto, netdev_get_name(ofport->netdev), + json_ofproto_port); + /* Add OpenFlow port associated with a bridge port. */ + json_object_put_format(json_ofproto_port, "ofport", "%u", + ofport->ofp_port); + + /* Add bridge port number. */ + odp_port_t odp_port = ofp_port_to_odp_port(ofproto, + ofport->ofp_port); + if (odp_port != ODPP_NONE) { + json_object_put_format(json_ofproto_port, "port_no", + "%"PRIu32, odp_port); + } else { + json_object_put_string(json_ofproto_port, "port_no", "none"); + } + + /* Add type of a bridge port. */ + json_object_put_string(json_ofproto_port, "type", + netdev_get_type(ofport->netdev)); + + /* Add config entries for a bridge port. */ + struct json *json_port_config = json_object_create(); + struct smap config; + smap_init(&config); + + json_object_put(json_ofproto_port, "config", json_port_config); + if (!netdev_get_config(ofport->netdev, &config)) { + struct smap_node *node; + + SMAP_FOR_EACH (node, &config) { + json_object_put_string(json_port_config, node->key, + node->value); + } + } + smap_destroy(&config); + + } /* End of bridge port(s). */ + + free(ports); + } /* End of bridge(s). */ + shash_destroy(&ofproto_shash); + free(ofprotos); + + return json_backer; +} + static void -dpif_show_backer(const struct dpif_backer *backer, struct ds *ds) +dpif_show_backer_text(const struct dpif_backer *backer, struct ds *ds) { const struct shash_node **ofprotos; struct dpif_dp_stats dp_stats; @@ -6587,18 +6679,24 @@ static void ofproto_unixctl_dpif_show(struct unixctl_conn *conn, int argc OVS_UNUSED, const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) { - struct ds ds = DS_EMPTY_INITIALIZER; - const struct shash_node **backers; - int i; + if (unixctl_command_get_output_format(conn) == UNIXCTL_OUTPUT_FMT_JSON) { + struct json *backers = json_object_create(); + const struct shash_node *backer; + SHASH_FOR_EACH (backer, &all_dpif_backers) { + dpif_show_backer_json(backers, backer->data); + } + unixctl_command_reply_json(conn, backers); + } else { + struct ds ds = DS_EMPTY_INITIALIZER; + const struct shash_node **backers = shash_sort(&all_dpif_backers); + for (int i = 0; i < shash_count(&all_dpif_backers); i++) { + dpif_show_backer_text(backers[i]->data, &ds); + } + free(backers); - backers = shash_sort(&all_dpif_backers); - for (i = 0; i < shash_count(&all_dpif_backers); i++) { - dpif_show_backer(backers[i]->data, &ds); + unixctl_command_reply(conn, ds_cstr(&ds)); + ds_destroy(&ds); } - free(backers); - - unixctl_command_reply(conn, ds_cstr(&ds)); - ds_destroy(&ds); } static void diff --git a/tests/pmd.at b/tests/pmd.at index 35a44b4df..189138f81 100644 --- a/tests/pmd.at +++ b/tests/pmd.at @@ -112,6 +112,29 @@ dummy@ovs-dummy: hit:0 missed:0 p0 1/1: (dummy-pmd: n_rxq=1, n_txq=1, numa_id=0) ]) +AT_CHECK([ovs-appctl --format json --pretty dpif/show], [0], [dnl +[{ + "dummy@ovs-dummy": { + "bridges": { + "br0": { + "br0": { + "config": { + }, + "ofport": "65534", + "port_no": "100", + "type": "dummy-internal"}, + "p0": { + "config": { + "n_rxq": "1", + "n_txq": "1", + "numa_id": "0"}, + "ofport": "1", + "port_no": "1", + "type": "dummy-pmd"}}}, + "stats": { + "n_hit": "0", + "n_missed": "0"}}}]]) + OVS_VSWITCHD_STOP AT_CLEANUP