From patchwork Thu Jul 4 14:09:41 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakob Meng X-Patchwork-Id: 1956898 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=MqsNf51D; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::136; helo=smtp3.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136]) (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 4WFJSp0dFQz1xpP for ; Fri, 5 Jul 2024 00:10:17 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 74CCE60EC5; Thu, 4 Jul 2024 14:10:15 +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 tkUnmtb1EKRe; Thu, 4 Jul 2024 14:10:10 +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 smtp3.osuosl.org 0E25960BA5 Authentication-Results: smtp3.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=MqsNf51D Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp3.osuosl.org (Postfix) with ESMTPS id 0E25960BA5; Thu, 4 Jul 2024 14:10:08 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 6A90FC0A9C; Thu, 4 Jul 2024 14:10:07 +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 1A27DC0A99 for ; Thu, 4 Jul 2024 14:10:02 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id A476C847BF for ; Thu, 4 Jul 2024 14:10:00 +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 9OsqI8iir4Af for ; Thu, 4 Jul 2024 14:09:58 +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 smtp1.osuosl.org 7C50E847A5 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 7C50E847A5 Authentication-Results: smtp1.osuosl.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=MqsNf51D Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by smtp1.osuosl.org (Postfix) with ESMTPS id 7C50E847A5 for ; Thu, 4 Jul 2024 14:09:58 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1720102197; 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=zV3nQ4HsxiHraStSrICjSMUDdd0lbznvgt1t7WkaImk=; b=MqsNf51DP6seB6SWXWgsk24EosNsIubxybIighmpFzaPm+j6+VvIvOZWIeZdKgO+4UErge ROvLm6cPaTPDhQI4ZzjDEtcALKlgKSwsc9nZ8xtnS0d3cilGEs8l1gP+01c0YzgdUNIIxd fzkUKeu2hsKDnt9P6yBx0T+Deoyj2u4= Received: from mail-lj1-f197.google.com (mail-lj1-f197.google.com [209.85.208.197]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-634-bYi0rhX9MkqqpaP3P9nWHA-1; Thu, 04 Jul 2024 10:09:56 -0400 X-MC-Unique: bYi0rhX9MkqqpaP3P9nWHA-1 Received: by mail-lj1-f197.google.com with SMTP id 38308e7fff4ca-2ee92048377so2477531fa.2 for ; Thu, 04 Jul 2024 07:09:55 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720102194; x=1720706994; 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=zV3nQ4HsxiHraStSrICjSMUDdd0lbznvgt1t7WkaImk=; b=Jh4tYPV3a96+KSiA963z5rvCNUUPKx4q4kXSN53rddmnJYIHJNAxcanJbgjuCCS6hL 6d2qRAhCr3+EeyrbYpvy6SLeqyr9nm/gsY2BgTQVD9oE/p2zURDFEJIOijc9azHBOWRd DXiy+i2HAXn8F7ayiUMlvVsxaJ0X8V/HUGEWt1ROyRg6E8Um6qPtq0V5Tu4j73B7+fFs Df91pcSRWn9jSNsBXySpaWRtx0yN4cf3AieMPPlPnkHlvt31Alkuvt+8QUGoY+jQgv1g wGqvc0p7wKuvKfZA7gJmtEy4ev/RCncD3YprNo5SPzATQFJwYRWfgDkhmSAl8uzFZzW5 HKjg== X-Gm-Message-State: AOJu0Yz7f/KwHonB4pdYTrcqOAKZdmrHPSlELHBVxnoY2eSeWvIMgfH0 IQJ/1yjGhYW84mVtW5UnqeUgixtCikR00z302ttFcfp1CA7BUCu5zrgORXzu6MhHI9bV7HI/JxP O5jnCJZQtcV+KNbSsqE3aEH4ZpvaXyeSP02F4YpThDr9OODHFctUBr+o7vChCSOUgOW+lruOAh7 vrgXgNJRXQbsOr2JVcUaHtRLwTfvaZ X-Received: by 2002:a2e:9851:0:b0:2ee:64cf:f8f3 with SMTP id 38308e7fff4ca-2ee8edff039mr13492161fa.33.1720102194329; Thu, 04 Jul 2024 07:09:54 -0700 (PDT) X-Google-Smtp-Source: AGHT+IGA4wvEbJOw+mcNi7feJNJ4IG69isjgkwQcC7ELSjwNBtim1J86vqdUqTL653F9qSstBX42yw== X-Received: by 2002:a2e:9851:0:b0:2ee:64cf:f8f3 with SMTP id 38308e7fff4ca-2ee8edff039mr13491711fa.33.1720102193663; Thu, 04 Jul 2024 07:09:53 -0700 (PDT) Received: from positronik4lide.redhat.com ([87.122.59.144]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4264a1d50b0sm26565645e9.7.2024.07.04.07.09.52 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 04 Jul 2024 07:09:53 -0700 (PDT) From: jmeng@redhat.com To: dev@openvswitch.org, i.maximets@ovn.org Date: Thu, 4 Jul 2024 16:09:41 +0200 Message-Id: <20240704140946.838801-2-jmeng@redhat.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240704140946.838801-1-jmeng@redhat.com> References: <20240704140946.838801-1-jmeng@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v13 1/6] Add global option for JSON output to ovs-appctl. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 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 | 183 ++++++++++++++++++++++------- lib/unixctl.h | 17 ++- lib/util.c | 6 +- python/ovs/unixctl/server.py | 3 - tests/appctl.py | 5 + tests/ovs-vswitchd.at | 12 ++ utilities/ovs-appctl.c | 135 ++++++++++++++++++--- 9 files changed, 307 insertions(+), 69 deletions(-) diff --git a/Documentation/ref/ovs-appctl.8.rst b/Documentation/ref/ovs-appctl.8.rst index 3ce02e984..148cc7632 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 e0359b759..f182647c7 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ Post-v3.3.0 -------------------- - Option '--mlockall' now only locks memory pages on fault, if possible. This also makes it compatible with vHost Post-copy Live Migration. + - 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..66f5de163 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,30 @@ 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 ""; + } +} + +bool +unixctl_output_fmt_from_string(const char *string, + enum unixctl_output_fmt *fmt) +{ + if (!strcasecmp(string, "text")) { + *fmt = UNIXCTL_OUTPUT_FMT_TEXT; + } else if (!strcasecmp(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 +122,53 @@ 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]; + + switch (parsed_option->o->val) { + case 'f': + /* 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; + } + break; + + default: + 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 +203,35 @@ 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; +} + +/* 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 +243,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 +353,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]", 1, 2, + unixctl_set_options, NULL); struct unixctl_server *server = xmalloc(sizeof *server); server->listener = listener; @@ -381,6 +486,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,7 +589,7 @@ 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; @@ -506,26 +612,17 @@ 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; - } + if (reply->result && reply->error) { + VLOG_WARN("unexpected response when communicating with %s: %s\n %s", + jsonrpc_get_name(client), + json_to_string(reply->result, JSSF_SORT), + json_to_string(reply->error, JSSF_SORT)); + return EINVAL; } + *result = json_nullable_clone(reply->result); + *err = json_nullable_clone(reply->error); + jsonrpc_msg_destroy(reply); return error; } diff --git a/lib/unixctl.h b/lib/unixctl.h index 4562dbc49..85d2000e0 100644 --- a/lib/unixctl.h +++ b/lib/unixctl.h @@ -17,10 +17,21 @@ #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); +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 +47,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 +56,12 @@ 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_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 3a6351a2f..84e8c4966 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..b1ae1ae1e 100644 --- a/tests/ovs-vswitchd.at +++ b/tests/ovs-vswitchd.at @@ -265,3 +265,15 @@ 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..721698755 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 "openvswitch/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,30 @@ 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) { + ovs_error(0, "Unexpected reply type in JSON rpc reply: %s", + json_type_to_string(reply->type)); + exit(2); + } + + struct ds ds = DS_EMPTY_INITIALIZER; + + if (fmt == UNIXCTL_OUTPUT_FMT_TEXT) { + ds_put_cstr(&ds, json_string(reply)); + } else { + json_to_ds(reply, JSSF_SORT, &ds); + } + + if (ds_last(&ds) != EOF && ds_last(&ds) != '\n') { + ds_put_char(&ds, '\n'); + } + + return ds_steal_cstr(&ds); +} From patchwork Thu Jul 4 14:09:42 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakob Meng X-Patchwork-Id: 1956894 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=iqzr4Xv6; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::133; helo=smtp2.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::133]) (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 4WFJSc1mKjz1xqs for ; Fri, 5 Jul 2024 00:10:08 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id 898F540621; Thu, 4 Jul 2024 14:10:04 +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 hxPAlv3j2TwV; Thu, 4 Jul 2024 14:10:03 +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 smtp2.osuosl.org ED1504010D Authentication-Results: smtp2.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=iqzr4Xv6 Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp2.osuosl.org (Postfix) with ESMTPS id ED1504010D; Thu, 4 Jul 2024 14:10:02 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 5E351C0A96; Thu, 4 Jul 2024 14:10:02 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138]) by lists.linuxfoundation.org (Postfix) with ESMTP id 3E75BC0A96 for ; Thu, 4 Jul 2024 14:10:00 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 1FA23847C0 for ; Thu, 4 Jul 2024 14:10:00 +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 qRt2_CNGguBk for ; Thu, 4 Jul 2024 14:09:59 +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 CA3AA847B3 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 CA3AA847B3 Authentication-Results: smtp1.osuosl.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=iqzr4Xv6 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 CA3AA847B3 for ; Thu, 4 Jul 2024 14:09:58 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1720102197; 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=JV6sB9Zg/+kqyW7gow4AJ29JKRyjgvza2y5bLP6XqV4=; b=iqzr4Xv6Xv0IKCOliweGX0GcIN9v1+Wy9sn9cRQPIQ/xHbGMPkWIHU5Y7uWldpbInG/lmY CiKM1vmWi0NoJEBVn4e0vqPfIVXP0lZ0ZHxI0c4r+g0oxi8lkiOsH8JNQvd1GYJuPNG3RN 3GwCumZDjAk2F+34BOlCK/sG2BoJ/EE= Received: from mail-wm1-f69.google.com (mail-wm1-f69.google.com [209.85.128.69]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-659-pWU1QkzpPp-QQC0RSvqCQg-1; Thu, 04 Jul 2024 10:09:56 -0400 X-MC-Unique: pWU1QkzpPp-QQC0RSvqCQg-1 Received: by mail-wm1-f69.google.com with SMTP id 5b1f17b1804b1-4257f95ffc6so5088065e9.1 for ; Thu, 04 Jul 2024 07:09:55 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720102195; x=1720706995; 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=JV6sB9Zg/+kqyW7gow4AJ29JKRyjgvza2y5bLP6XqV4=; b=pKe36gd8GMdISVzRauS7cccyBw6ASIFYY3TgrqpFOZmH/xDVFBKq5X0lsmXcQgEQYv WR8f5KQ3mbn3ZYhGg9GrYeoiXFWCf2c8aFsvN6cOFyPM4lIJBt8sl3E20Uka22VOlhSw aPRzzw5f2X8oWHPAyYD9V7MWvsyOhqJIwKpyLtujTI1Ym+XORbo8M5Lnx4qQD99kvsHy b4cOUqHw971a7/lcK7+HZ9McXI9pDN5rRcosakq2TAXMyjk6CNJ7cpOw7xAPqNzu4SGc 8wzkZ4dUzC10JuC7NqjAkBCbqNi1wJqmqIONAg28SLRP0wzffNAbnDta15w4R1ULieB3 Woug== X-Gm-Message-State: AOJu0YwjlM/hPAitWLRQdHpLBTIwTpm5vCSIx1YoVqnh9N7TsaTqcfIR RU/FdGdsraCrvlC9e/LZa/NmHr2Sq+pP2IeZvGjMAg9QXESY9L170cu15rGS/lHufelySPNoEiv eKrE1RL+dAIvA2lyWRI+R+1cRKz1pJ3bPVZTx4BkjVjGQic52FuqO8DGu9l2W/PEgJKHee0Y+tq GKpPhvzAkC6aela0Qn7CRhCqWcGnOX X-Received: by 2002:a7b:c056:0:b0:426:491e:6252 with SMTP id 5b1f17b1804b1-4264a3d7676mr12854395e9.11.1720102194971; Thu, 04 Jul 2024 07:09:54 -0700 (PDT) X-Google-Smtp-Source: AGHT+IGMuy73aGWdBwqFdqsSht2Oy+yxTjvKw9OUACMNV3F/aBrjKhOhtmDa+6mPAb9ZdZSg9zT7cw== X-Received: by 2002:a7b:c056:0:b0:426:491e:6252 with SMTP id 5b1f17b1804b1-4264a3d7676mr12854185e9.11.1720102194398; Thu, 04 Jul 2024 07:09:54 -0700 (PDT) Received: from positronik4lide.redhat.com ([87.122.59.144]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4264a1d50b0sm26565645e9.7.2024.07.04.07.09.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 04 Jul 2024 07:09:54 -0700 (PDT) From: jmeng@redhat.com To: dev@openvswitch.org, i.maximets@ovn.org Date: Thu, 4 Jul 2024 16:09:42 +0200 Message-Id: <20240704140946.838801-3-jmeng@redhat.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240704140946.838801-1-jmeng@redhat.com> References: <20240704140946.838801-1-jmeng@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v13 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.30 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 | 2 ++ python/ovs/unixctl/__init__.py | 8 ++++++ python/ovs/unixctl/client.py | 5 ++-- python/ovs/unixctl/server.py | 52 +++++++++++++++++++++++++++++----- tests/appctl.py | 38 ++++++++++++++++++++----- tests/unixctl-py.at | 4 +++ 6 files changed, 93 insertions(+), 16 deletions(-) diff --git a/NEWS b/NEWS index f182647c7..c750ebae2 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,8 @@ Post-v3.3.0 per interface 'options:dpdk-lsc-interrupt' to 'false'. - Python: * Added custom transaction support to the Idl via add_op(). + * Added support for different output formats like 'json' to Python's + unixctl classes. v3.3.0 - 16 Feb 2024 diff --git a/python/ovs/unixctl/__init__.py b/python/ovs/unixctl/__init__.py index 8ee312943..b05f3df72 100644 --- a/python/ovs/unixctl/__init__.py +++ b/python/ovs/unixctl/__init__.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import enum import sys import ovs.util @@ -19,6 +20,13 @@ import ovs.util commands = {} +@enum.unique +# FIXME: Use @enum.verify(enum.NAMED_FLAGS) from Python 3.11 when available. +class UnixctlOutputFormat(enum.IntFlag): + TEXT = 1 << 0 + JSON = 1 << 1 + + class _UnixctlCommand(object): def __init__(self, usage, min_args, max_args, callback, aux): self.usage = usage 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..855f38d18 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.unixctl.UnixctlOutputFormat.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.unixctl.UnixctlOutputFormat.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.unixctl.UnixctlOutputFormat]) + + try: + args = parser.parse_args(args=argv) + except argparse.ArgumentError as e: + conn.reply_error(str(e)) + return + + conn._fmt = ovs.unixctl.UnixctlOutputFormat[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]", 1, + 2, _unixctl_set_options, None) + return 0, UnixctlServer(listener) diff --git a/tests/appctl.py b/tests/appctl.py index e5cc28138..ce97a5dcf 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.unixctl.UnixctlOutputFormat.TEXT): + if fmt == ovs.unixctl.UnixctlOutputFormat.TEXT: + body = str(reply) + else: + body = ovs.json.to_string(reply) + + if body and not body.endswith("\n"): + body += "\n" + + return body + + 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.unixctl.UnixctlOutputFormat]) args = parser.parse_args() signal_alarm(int(args.timeout) if args.timeout else None) ovs.vlog.Vlog.init() target = args.target + format = ovs.unixctl.UnixctlOutputFormat[args.format.upper()] client = connect_to_target(target) + + if format != ovs.unixctl.UnixctlOutputFormat.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..4d59c5818 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,9 @@ 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 Jul 4 14:09:43 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakob Meng X-Patchwork-Id: 1956896 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=V8woA6xg; 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 4WFJSj0Dk2z1xpP for ; Fri, 5 Jul 2024 00:10:13 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 760B6847FD; Thu, 4 Jul 2024 14:10:10 +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 bvYJJKBUFySd; Thu, 4 Jul 2024 14:10:05 +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 0A2CA847DD 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=V8woA6xg Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp1.osuosl.org (Postfix) with ESMTPS id 0A2CA847DD; Thu, 4 Jul 2024 14:10:05 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 24129C0A99; Thu, 4 Jul 2024 14:10:04 +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 8E11DC0A96 for ; Thu, 4 Jul 2024 14:10:00 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id 7BFC74010D for ; Thu, 4 Jul 2024 14:10:00 +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 Qmq_SE6FojQP for ; Thu, 4 Jul 2024 14:09:59 +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 smtp2.osuosl.org 265BF400A6 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 265BF400A6 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=V8woA6xg Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by smtp2.osuosl.org (Postfix) with ESMTPS id 265BF400A6 for ; Thu, 4 Jul 2024 14:09:58 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1720102198; 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=AKw2+/eWghh43qSdAvLTTP848g7PfX0Eq9FFGLUFGaI=; b=V8woA6xgeTRTDYgXH2HHQgbqaxV+x7t0Wtun+AFlBTDAXye8F8r6mMp6Z86ZnqV3aIdmeZ U4DT60k/icg+e577qUrZ83PMb+I9kgehXqxFfadweuZxJhCPdNvezbY/sfFz9R8AvIjhpc mqV9muW3/UMe4GreqgCDbQOGgAPwPUg= 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-643-WFF4Sb3tMuW12zexipd78g-1; Thu, 04 Jul 2024 10:09:56 -0400 X-MC-Unique: WFF4Sb3tMuW12zexipd78g-1 Received: by mail-wm1-f72.google.com with SMTP id 5b1f17b1804b1-4264dc624a5so1426275e9.1 for ; Thu, 04 Jul 2024 07:09:56 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720102195; x=1720706995; 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=AKw2+/eWghh43qSdAvLTTP848g7PfX0Eq9FFGLUFGaI=; b=F2GmP603TmlJ7YhRMBeNogdurfQ/fgd9Lr7t4rlXgjcC883MqvarFwQ2ZGMF0V2Ujg BwEZ/X1otRRDlObLTimEWAY8WT4SxqnENWVsxnok0XJqBy8xDVgxfSutg50rMu5swo6L 3y8tR0fVuEnSkXh9qwSzV4YEi9tm/Ycs+0BRv/8T5K7G1JQU8r/3ukUZTExngpEhILpg Tp4ma++5vtXOSnu3off9XnzxV2LhAakdRXbqrIKi1rpNxYfGtqsouyoB00EilIaSYSUp zOj93t1SRNjtSkMocVJH8eI6RErP1eT8FJySfUfsrZFX+Xrr8Qw1j/2r4m8CaagojPLB DvcQ== X-Gm-Message-State: AOJu0YybbGTHiv2lJFzZjl0cMfXeXzwwcGSotR8WwjoBY7nFNLIceFL0 FTEZ+JxUX8j1gH2aI8aM+XVzkZr349M5pnOP3tBZTEulwtYXnDGTgtKSwYZqzC5NKEtcAIVrjgT SBt9H8GWyImRMmajjkN3fyD9UAcNVf6XT/lNUFDGqnFRRgS99CJi1WyMwb2OcWir3w4o0BE85Od ec6I+gI9ozmxTqNa8k/mj1LqElepzv X-Received: by 2002:a05:600c:12cc:b0:425:65b1:abbc with SMTP id 5b1f17b1804b1-4264a3d96f8mr13262285e9.1.1720102195518; Thu, 04 Jul 2024 07:09:55 -0700 (PDT) X-Google-Smtp-Source: AGHT+IGbkyhEhN3b7lOHtBIx3AW6uyTFfblhJolxIZrym5lNFfrifZhLlrBo6sjDGg/UQRTf7aruvw== X-Received: by 2002:a05:600c:12cc:b0:425:65b1:abbc with SMTP id 5b1f17b1804b1-4264a3d96f8mr13262085e9.1.1720102195107; Thu, 04 Jul 2024 07:09:55 -0700 (PDT) Received: from positronik4lide.redhat.com ([87.122.59.144]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4264a1d50b0sm26565645e9.7.2024.07.04.07.09.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 04 Jul 2024 07:09:54 -0700 (PDT) From: jmeng@redhat.com To: dev@openvswitch.org, i.maximets@ovn.org Date: Thu, 4 Jul 2024 16:09:43 +0200 Message-Id: <20240704140946.838801-4-jmeng@redhat.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240704140946.838801-1-jmeng@redhat.com> References: <20240704140946.838801-1-jmeng@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v13 3/6] appctl: Add option '--pretty' for pretty-printing JSON output. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 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 | 8 +++++++ NEWS | 1 + tests/ovs-vswitchd.at | 6 ++++++ utilities/ovs-appctl.c | 34 ++++++++++++++++++++++++------ 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/Documentation/ref/ovs-appctl.8.rst b/Documentation/ref/ovs-appctl.8.rst index 148cc7632..7054cf559 100644 --- a/Documentation/ref/ovs-appctl.8.rst +++ b/Documentation/ref/ovs-appctl.8.rst @@ -9,6 +9,7 @@ Synopsis [``--target=`` | ``-t`` ] [``--timeout=`` | ``-T`` ] [``--format=`` | ``-f`` ] +[``--pretty``] [...] ``ovs-appctl --help`` @@ -79,6 +80,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 c750ebae2..d903d2f74 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,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. - 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/tests/ovs-vswitchd.at b/tests/ovs-vswitchd.at index b1ae1ae1e..0f7a6085e 100644 --- a/tests/ovs-vswitchd.at +++ b/tests/ovs-vswitchd.at @@ -276,4 +276,10 @@ 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 721698755..682ee100c 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); @@ -324,7 +344,7 @@ reply_to_string(struct json *reply, enum unixctl_output_fmt fmt) if (fmt == UNIXCTL_OUTPUT_FMT_TEXT) { ds_put_cstr(&ds, json_string(reply)); } else { - json_to_ds(reply, JSSF_SORT, &ds); + json_to_ds(reply, JSSF_SORT | fmt_flags, &ds); } if (ds_last(&ds) != EOF && ds_last(&ds) != '\n') { From patchwork Thu Jul 4 14:09:44 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakob Meng X-Patchwork-Id: 1956895 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=K6IOK2Gm; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::133; helo=smtp2.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::133]) (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 4WFJSg07mbz1xqq for ; Fri, 5 Jul 2024 00:10:10 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id 219AA408C4; Thu, 4 Jul 2024 14:10:08 +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 F7VW-Z4v24d8; Thu, 4 Jul 2024 14:10:06 +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 smtp2.osuosl.org 508D640604 Authentication-Results: smtp2.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=K6IOK2Gm Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp2.osuosl.org (Postfix) with ESMTPS id 508D640604; Thu, 4 Jul 2024 14:10:06 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 164ABC0A9F; Thu, 4 Jul 2024 14:10:06 +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 9912AC0A97 for ; Thu, 4 Jul 2024 14:10:01 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 59455847B3 for ; Thu, 4 Jul 2024 14:10:00 +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 PPFtaoucO3Hh for ; Thu, 4 Jul 2024 14:09:59 +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 69DEE847BF 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 69DEE847BF Authentication-Results: smtp1.osuosl.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=K6IOK2Gm 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 69DEE847BF for ; Thu, 4 Jul 2024 14:09:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1720102198; 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=+aoT/Q/7avfPfiAvEzzW6XlbLG3r7WpykZb9wi/w1fQ=; b=K6IOK2GmAmyEb83A7UJOxuR+iRImvlPfe2NvCCr0wcQSZ7C9u84BueyYTyzsSJ5mq7m6q9 TN1wIq5NXjTzC1CfjelojY/HSaaxVv/xiz+oCk2KR0FVUloD9On6rm3/UpqFIRULKiLYKS bVVgvKEPqgD2cwxI5Vl8HGwZ0AhzfJQ= Received: from mail-wm1-f71.google.com (mail-wm1-f71.google.com [209.85.128.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-647-OiNJTZ6ZP8CgehzCb4gOKA-1; Thu, 04 Jul 2024 10:09:57 -0400 X-MC-Unique: OiNJTZ6ZP8CgehzCb4gOKA-1 Received: by mail-wm1-f71.google.com with SMTP id 5b1f17b1804b1-4258675a531so4678605e9.3 for ; Thu, 04 Jul 2024 07:09:56 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720102196; x=1720706996; 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=+aoT/Q/7avfPfiAvEzzW6XlbLG3r7WpykZb9wi/w1fQ=; b=QbjSchP7lOtsghZtE4XG1nSFfWzJ3/cZ0f8exPcQQ4N5knD9dhl8McERTvYz01JJPi uoM2za2L+picH/Kw1DAzvL0yJ61OV0nnWqSoltIeJHu3kuMTlxOdwlQQTZiSXhkKpRgS PJFgXNlyKnNy8sXc2ViKfx3Ajmia03h2MxCbC8kWSv113Zgdrd0pVVRCe6DrBcUhlLOG gwxYWE7h5owJdpGtZfLpH5CiEpMGDhXPOHjxmAXIqpJ7tNdGCpafEzJd80H2RpAOeOxO v1DTF/d8Az/G5ffGt+N62SyfTqeixUrCqfw/zJDWT1O5+fTynjtn+xLHLGhsHjOqeebh 6x9Q== X-Gm-Message-State: AOJu0YyBs8WKCkPDfuO/FllsVfkyQaCjhlsCeL6T3L5n+9dsMKfD4hZl w7pLUarn28TqAjMPrbdcwqwpc3ojIgMgYx4RQEHlES/J7toqERwXW9IvBA9c/zu+F85W+IeG8b6 nL3scUY+opV0Jg8Gv2wbcb1N7yvQjPFIyX8c9j5R9IjRIcBNJp3kDo5juNrTHuX8kIMP4eumelC 4Nyg6wcTep0+lKuQrdLxnVClDgMUzo X-Received: by 2002:a05:600c:364c:b0:425:7a99:e6f2 with SMTP id 5b1f17b1804b1-4264a3e31bfmr15545615e9.14.1720102196095; Thu, 04 Jul 2024 07:09:56 -0700 (PDT) X-Google-Smtp-Source: AGHT+IE5OYFHayvkFRz9GPNdM9bK6ACdBqW5OC1ZYOMOnTgnxFC+/HRJY4Cr9A8ZcLwziuQxcTyYww== X-Received: by 2002:a05:600c:364c:b0:425:7a99:e6f2 with SMTP id 5b1f17b1804b1-4264a3e31bfmr15545455e9.14.1720102195778; Thu, 04 Jul 2024 07:09:55 -0700 (PDT) Received: from positronik4lide.redhat.com ([87.122.59.144]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4264a1d50b0sm26565645e9.7.2024.07.04.07.09.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 04 Jul 2024 07:09:55 -0700 (PDT) From: jmeng@redhat.com To: dev@openvswitch.org, i.maximets@ovn.org Date: Thu, 4 Jul 2024 16:09:44 +0200 Message-Id: <20240704140946.838801-5-jmeng@redhat.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240704140946.838801-1-jmeng@redhat.com> References: <20240704140946.838801-1-jmeng@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v13 4/6] python: Add option for pretty-printing JSON output to appctl.py. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 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 | 15 ++++++++++++--- tests/unixctl-py.at | 6 ++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/appctl.py b/tests/appctl.py index ce97a5dcf..230208a19 100644 --- a/tests/appctl.py +++ b/tests/appctl.py @@ -37,11 +37,12 @@ def connect_to_target(target): return client -def reply_to_string(reply, fmt=ovs.unixctl.UnixctlOutputFormat.TEXT): +def reply_to_string(reply, fmt=ovs.unixctl.UnixctlOutputFormat.TEXT, + fmt_flags={}): if fmt == ovs.unixctl.UnixctlOutputFormat.TEXT: body = str(reply) else: - body = ovs.json.to_string(reply) + body = ovs.json.to_string(reply, **fmt_flags) if body and not body.endswith("\n"): body += "\n" @@ -65,13 +66,21 @@ def main(): help="Output format.", default="text", choices=[fmt.name.lower() for fmt in ovs.unixctl.UnixctlOutputFormat]) + 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.unixctl.UnixctlOutputFormat.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.unixctl.UnixctlOutputFormat[args.format.upper()] + format_flags = dict(pretty=True) if args.pretty else {} client = connect_to_target(target) if format != ovs.unixctl.UnixctlOutputFormat.TEXT: @@ -96,7 +105,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 4d59c5818..0106af825 100644 --- a/tests/unixctl-py.at +++ b/tests/unixctl-py.at @@ -116,6 +116,12 @@ 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 Jul 4 14:09:45 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakob Meng X-Patchwork-Id: 1956897 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=QQdrXhd3; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::133; helo=smtp2.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::133]) (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 4WFJSk3QMvz1xpP for ; Fri, 5 Jul 2024 00:10:14 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id A802540BFC; Thu, 4 Jul 2024 14:10:11 +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 GzvgYHurda4Z; Thu, 4 Jul 2024 14:10:10 +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 smtp2.osuosl.org 6156740BFF Authentication-Results: smtp2.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=QQdrXhd3 Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp2.osuosl.org (Postfix) with ESMTPS id 6156740BFF; Thu, 4 Jul 2024 14:10:09 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id EE539C0A97; Thu, 4 Jul 2024 14:10:08 +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 A0B65C0A99 for ; Thu, 4 Jul 2024 14:10:02 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 6408360832 for ; Thu, 4 Jul 2024 14:10:02 +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 NT9aamazmYuD for ; Thu, 4 Jul 2024 14:10:01 +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 3B4E960830 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 3B4E960830 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=QQdrXhd3 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 3B4E960830 for ; Thu, 4 Jul 2024 14:10:01 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1720102200; 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=c7saXO22N4b/5GeLpP4sH90mOt6RZeC+zjOU/fOfyHY=; b=QQdrXhd3BNUrPT2V7qzTG/9J4ac4xPNIVxQZ1LIBdjh40ZVvD9NLmSysuVXZyNuLwRHXF1 AIFaoukeEKiOIGM9pxrq6S61y1NlFIvDqRpW05jBirYK//OPHCLM8O8gnEPBVmKWDGxOya 1NaNL6Rr/B/Uolura/D0N9erwsw352I= Received: from mail-wm1-f69.google.com (mail-wm1-f69.google.com [209.85.128.69]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-85-27YV8qe7Nu2H_1CxBMCIUA-1; Thu, 04 Jul 2024 10:09:59 -0400 X-MC-Unique: 27YV8qe7Nu2H_1CxBMCIUA-1 Received: by mail-wm1-f69.google.com with SMTP id 5b1f17b1804b1-42566e8a9efso6786875e9.2 for ; Thu, 04 Jul 2024 07:09:57 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720102197; x=1720706997; 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=c7saXO22N4b/5GeLpP4sH90mOt6RZeC+zjOU/fOfyHY=; b=N/YpjL0FN8kEq3o0YOPV9ACXYHvpBoF/O0nQcm4LJ7QFQ63XKWo4yQi151GEuaDgf+ /+VxZX/6R56Og69D9KPIsliGimwmzGLHLAOWvUkeLg13MDmO/yu8pN/D6C8hn7NlzF+s qfEuNJZE2Yo9AFIzbaOQOBuSHtOTTqa5NHFHx2Esp13GEOdgNtvwFbu9wpp2ee6UJZKO YXVuXsMc7ssS24NjfhbNJhBGIJFrTl9b8FDm17WN/jzdlUKOROYUbS0+4bt6XpH3Wkv3 rQfigp/1ICXKkHBEEW9DDZa7NgAcGikXk++upAscbSBHPHprO8vcpCYn1ckbTHnCWR+a kKxg== X-Gm-Message-State: AOJu0YxQXQN0E2z9icrR/7U8m+LiyGWvg2+1sCpNlw8LJvOs4EfBsBnl tnkW2zdUlKXhWQC9CyJNwKET/AxeZkSoeZE8MGeRy2w+lMvTBXMmQ7/HFaThgnGgx3K2QsLunV6 dLKDTDxKsSZfhRX9I9BQOKMw4qo53BoKYF+ZDhLZR1ENxm9ANiVY55fH39Yi6ufjO1EPVw5oOwV LR9zD9gzbCCSCLDDOH3hMJZ2NCND6P X-Received: by 2002:a05:600c:4da2:b0:424:ac9f:5c68 with SMTP id 5b1f17b1804b1-4264a3f2fa8mr17022215e9.29.1720102196930; Thu, 04 Jul 2024 07:09:56 -0700 (PDT) X-Google-Smtp-Source: AGHT+IELdemZ8uQdsWgVNxdlCDSI9RJ5wpKyLK1FGxzHQKRhGLgDRhadMf3+I2VUuZqSotQiUhn3vw== X-Received: by 2002:a05:600c:4da2:b0:424:ac9f:5c68 with SMTP id 5b1f17b1804b1-4264a3f2fa8mr17022005e9.29.1720102196570; Thu, 04 Jul 2024 07:09:56 -0700 (PDT) Received: from positronik4lide.redhat.com ([87.122.59.144]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4264a1d50b0sm26565645e9.7.2024.07.04.07.09.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 04 Jul 2024 07:09:56 -0700 (PDT) From: jmeng@redhat.com To: dev@openvswitch.org, i.maximets@ovn.org Date: Thu, 4 Jul 2024 16:09:45 +0200 Message-Id: <20240704140946.838801-6-jmeng@redhat.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240704140946.838801-1-jmeng@redhat.com> References: <20240704140946.838801-1-jmeng@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v13 5/6] vswitchd: Add JSON output for 'list-commands' command. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 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 | 4 ++++ lib/unixctl.c | 44 +++++++++++++++++++++++++++++-------------- tests/ovs-vswitchd.at | 15 +++++++++++++++ 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/NEWS b/NEWS index d903d2f74..8c59a4979 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,10 @@ 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. - 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 66f5de163..f1e241e28 100644 --- a/lib/unixctl.c +++ b/lib/unixctl.c @@ -95,24 +95,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 0f7a6085e..730363e83 100644 --- a/tests/ovs-vswitchd.at +++ b/tests/ovs-vswitchd.at @@ -283,3 +283,18 @@ AT_CHECK_UNQUOTED([ovs-appctl --format json --pretty version], [0], [dnl ]) 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 with a trailing newline. +AT_CHECK([wc -l stdout], [0], [1 stdout +]) + +# Check that ovs-appctl prints a JSON document. +AT_CHECK([ovstest test-json stdout], [0], [ignore]) + +AT_CLEANUP From patchwork Thu Jul 4 14:09:46 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakob Meng X-Patchwork-Id: 1956899 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=PgqWx83z; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::137; helo=smtp4.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::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 4WFJSs3rdLz1xpP for ; Fri, 5 Jul 2024 00:10:21 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 6F3BA4141E; Thu, 4 Jul 2024 14:10:19 +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 CaVHN5QYctc4; Thu, 4 Jul 2024 14:10:16 +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 smtp4.osuosl.org 973FA41365 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=PgqWx83z Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp4.osuosl.org (Postfix) with ESMTPS id 973FA41365; Thu, 4 Jul 2024 14:10:11 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 23F27C0AA0; Thu, 4 Jul 2024 14:10:11 +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 01A59C0A9A for ; Thu, 4 Jul 2024 14:10:10 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id E92EC84816 for ; Thu, 4 Jul 2024 14:10:09 +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 h3nYTmf_bZ3w for ; Thu, 4 Jul 2024 14:10:06 +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 7F3A2847F1 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 7F3A2847F1 Authentication-Results: smtp1.osuosl.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=PgqWx83z 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 7F3A2847F1 for ; Thu, 4 Jul 2024 14:10:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1720102205; 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=W4oqA2SLD4Hzvvb2iKFopvOp9fzOuce9FVpdX3eYjwg=; b=PgqWx83zNW3iJ3YGDU38y1NXoQAVUOQ0W089RvfdDK8jvKZYOaVkornSgTurKIiF63kh33 sGeW4iIBnBxUMPwXeTR95pOZnFyRIrAxyyltoQ4XSkBQmnNKqjoAN/9uQpLYB4Zb/AKTIJ MoDahrz+XwWAvnrPxTGQUA1DhXoJFUY= Received: from mail-lj1-f199.google.com (mail-lj1-f199.google.com [209.85.208.199]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-263-tpZdww3jNKmBPgCOMgpU6w-1; Thu, 04 Jul 2024 10:10:01 -0400 X-MC-Unique: tpZdww3jNKmBPgCOMgpU6w-1 Received: by mail-lj1-f199.google.com with SMTP id 38308e7fff4ca-2ee49ce152eso7399351fa.3 for ; Thu, 04 Jul 2024 07:10:00 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720102200; x=1720707000; 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=W4oqA2SLD4Hzvvb2iKFopvOp9fzOuce9FVpdX3eYjwg=; b=gqsawHAxK+ztvJr/tFk8ECeaoPuBS7uEBsI70PBE664bLx9meKzXFGc5GD+OPyXK1Z mb81+rMWmgXQLjjGVA+XVh2PnAGbArZgsH9MuqGbwvFMQs/DUqfN+7wskTwPRoGeh1Tb pfBWL/Cu5HRZ8LOr6A9M8BPA9leUPZbcfJSdmfQM9r9qA334KDbyDyTlvjK+4VWCuw0Z rRuP2srhrrDAtBR04o0XjUEYrDNeBgs+S9UrpO27FAjis3hKo283gn6kBrESWLx7G4Ci j5is+cA//ZsYF3WtYffu/EX6YsILo3ScSk3+TB9MYuLj3lxDdSWgUV45ROXfb699k/P2 YTEQ== X-Gm-Message-State: AOJu0Yw2olBWbhIJSbw7Op5mg7iZ0PS/TCcKQGXiX48LyFdZw24cXgLK Ppp8s7pXUPmJ4gqaCMPGa0/1Krw1wRNtggB+qifQvHnOL7S/9f0NBiBacHlc8UCbW4cBflL+Yzg Rvo3LEYi+ZiQYEA+UGAWjx6BJWFRLjCECUuU8aHWaRvP/LPYDE/BPKfdfS5bsX+CNagb46fl1TO ywbQUHJht4ZozkFKxBoYlxVDiTEvLy X-Received: by 2002:a2e:a267:0:b0:2ee:6b86:b0aa with SMTP id 38308e7fff4ca-2ee8ed41f9fmr10355331fa.17.1720102199698; Thu, 04 Jul 2024 07:09:59 -0700 (PDT) X-Google-Smtp-Source: AGHT+IF7kiWbTvyt27dyzRW8LuIwsC5ROhrasZ361jT+gRu3S1urfhrk/VXc0ML8H6OyJZvMAETEZw== X-Received: by 2002:a2e:a267:0:b0:2ee:6b86:b0aa with SMTP id 38308e7fff4ca-2ee8ed41f9fmr10354771fa.17.1720102197291; Thu, 04 Jul 2024 07:09:57 -0700 (PDT) Received: from positronik4lide.redhat.com ([87.122.59.144]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4264a1d50b0sm26565645e9.7.2024.07.04.07.09.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 04 Jul 2024 07:09:56 -0700 (PDT) From: jmeng@redhat.com To: dev@openvswitch.org, i.maximets@ovn.org Date: Thu, 4 Jul 2024 16:09:46 +0200 Message-Id: <20240704140946.838801-7-jmeng@redhat.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240704140946.838801-1-jmeng@redhat.com> References: <20240704140946.838801-1-jmeng@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v13 6/6] ofproto: Add JSON output for 'dpif/show' command. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 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 | 2 +- ofproto/ofproto-dpif.c | 127 ++++++++++++++++++++++++++++++++++++----- tests/ofproto-dpif.at | 40 +++++++++++++ 3 files changed, 154 insertions(+), 15 deletions(-) diff --git a/NEWS b/NEWS index 8c59a4979..4803566f5 100644 --- a/NEWS +++ b/NEWS @@ -6,7 +6,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. + * 'dpif/show' and 'list-commands' now support 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 fcd7cd753..52d9b0c5b 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,19 +6520,109 @@ done: return changed; } -static void -dpif_show_backer(const struct dpif_backer *backer, struct ds *ds) +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 json *json_dp_stats = json_object_create(); + struct dpif_dp_stats dp_stats; + + dpif_get_dp_stats(backer->dpif, &dp_stats); + json_object_put_format(json_dp_stats, "hit", "%"PRIu64, dp_stats.n_hit); + json_object_put_format(json_dp_stats, "missed", "%"PRIu64, + dp_stats.n_missed); + json_object_put(json_backer, "stats", json_dp_stats); + + /* Add datapath's bridges under "bridges" key. */ + struct json *json_dp_bridges = json_object_create(); + + struct shash ofproto_shash = SHASH_INITIALIZER(&ofproto_shash); + free(get_ofprotos(&ofproto_shash)); + + struct shash_node *node; + SHASH_FOR_EACH (node, &ofproto_shash) { + struct ofproto_dpif *ofproto = node->data; + + if (ofproto->backer != backer) { + continue; + } + + /* Add bridge to "bridges" dictionary using its name as key. */ + struct json *json_ofproto = json_object_create(); + + /* Add bridge ports to the current bridge dictionary. */ + const struct shash_node *port; + SHASH_FOR_EACH (port, &ofproto->up.port_by_name) { + /* Add bridge port to a bridge's dict using port name as key. */ + struct ofport *ofport = port->data; + struct json *json_ofproto_port = json_object_create(); + + /* Add OpenFlow port associated with a bridge port. */ + json_object_put_format(json_ofproto_port, "ofport", "%"PRIu32, + 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 smap config = SMAP_INITIALIZER(&config); + if (!netdev_get_config(ofport->netdev, &config) + && smap_count(&config)) + { + struct json *json_port_config = json_object_create(); + struct smap_node *cfg_node; + + SMAP_FOR_EACH (cfg_node, &config) { + json_object_put_string(json_port_config, cfg_node->key, + cfg_node->value); + } + json_object_put(json_ofproto_port, "config", json_port_config); + } + smap_destroy(&config); + + + json_object_put(json_ofproto, netdev_get_name(ofport->netdev), + json_ofproto_port); + } /* End of bridge port(s). */ + + json_object_put(json_dp_bridges, ofproto->up.name, json_ofproto); + } /* End of bridge(s). */ + + shash_destroy(&ofproto_shash); + + json_object_put(json_backer, "bridges", json_dp_bridges); + return json_backer; +} + +static void +dpif_show_backer_text(const struct dpif_backer *backer, struct ds *ds) +{ + struct shash ofproto_shash = SHASH_INITIALIZER(&ofproto_shash); const struct shash_node **ofprotos; struct dpif_dp_stats dp_stats; - struct shash ofproto_shash; size_t i; dpif_get_dp_stats(backer->dpif, &dp_stats); ds_put_format(ds, "%s: hit:%"PRIu64" missed:%"PRIu64"\n", dpif_name(backer->dpif), dp_stats.n_hit, dp_stats.n_missed); - shash_init(&ofproto_shash); ofprotos = get_ofprotos(&ofproto_shash); for (i = 0; i < shash_count(&ofproto_shash); i++) { struct ofproto_dpif *ofproto = ofprotos[i]->data; @@ -6587,18 +6678,26 @@ 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; - backers = shash_sort(&all_dpif_backers); - for (i = 0; i < shash_count(&all_dpif_backers); i++) { - dpif_show_backer(backers[i]->data, &ds); + SHASH_FOR_EACH (backer, &all_dpif_backers) { + dpif_show_backer_json(backers, backer->data); + } + unixctl_command_reply_json(conn, backers); + } else { + const struct shash_node **backers = shash_sort(&all_dpif_backers); + struct ds ds = DS_EMPTY_INITIALIZER; + + for (int i = 0; i < shash_count(&all_dpif_backers); i++) { + dpif_show_backer_text(backers[i]->data, &ds); + } + free(backers); + + 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/ofproto-dpif.at b/tests/ofproto-dpif.at index 0b23fd6c5..30ef0468c 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -8879,6 +8879,46 @@ dummy@ovs-dummy: hit:0 missed:0 br1 65534/101: (dummy-internal) p3 3/3: (dummy) ]) + +AT_CHECK([ovs-appctl --format json --pretty dpif/show], [0], [dnl +[{ + "dummy@ovs-dummy": { + "bridges": { + "br0": { + "br0": { + "ofport": "65534", + "port_no": "100", + "type": "dummy-internal"}, + "p1": { + "config": { + "n_rxq": "1", + "n_txq": "1", + "numa_id": "0"}, + "ofport": "1", + "port_no": "1", + "type": "dummy-pmd"}, + "p2": { + "config": { + "n_rxq": "1", + "n_txq": "1", + "numa_id": "0"}, + "ofport": "2", + "port_no": "2", + "type": "dummy-pmd"}}, + "br1": { + "br1": { + "ofport": "65534", + "port_no": "101", + "type": "dummy-internal"}, + "p3": { + "ofport": "3", + "port_no": "3", + "type": "dummy"}}}, + "stats": { + "hit": "0", + "missed": "0"}}}] +]) + OVS_VSWITCHD_STOP AT_CLEANUP