From patchwork Thu Oct 12 10:04:26 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakob Meng X-Patchwork-Id: 1847353 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=P5InMAMg; 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 4S5lcy6Z5yz23jd for ; Thu, 12 Oct 2023 21:05:22 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 9DDEB408D6; Thu, 12 Oct 2023 10:05:20 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 9DDEB408D6 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=P5InMAMg X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id V-x4-iZjsd3H; Thu, 12 Oct 2023 10:05:18 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp4.osuosl.org (Postfix) with ESMTPS id 69EE740903; Thu, 12 Oct 2023 10:05:17 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 69EE740903 Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 3C9DEC0071; Thu, 12 Oct 2023 10:05:17 +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 B32C2C0032 for ; Thu, 12 Oct 2023 10:05:15 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 8E9AB81EAB for ; Thu, 12 Oct 2023 10:05:15 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp1.osuosl.org 8E9AB81EAB 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=P5InMAMg X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 9xVpYV5ue1V3 for ; Thu, 12 Oct 2023 10:05:13 +0000 (UTC) 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 954D981401 for ; Thu, 12 Oct 2023 10:05:13 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp1.osuosl.org 954D981401 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1697105112; 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; bh=ZP3YKIE2yTvGXlDgaajxVTn+QoCcFUX8u0IfhHPvSSM=; b=P5InMAMgGWH9XVQpkd6MWRKX46iexW1VE2QIShBRytf/lYZqiDxEhOMaUusoitQQIrpTq5 zoXx3/rB/ROr8t3G3KaNMNXJUw0Hot0YKuicMhiiw2vHQTVTtVSFA+euAKUuBQwM8QVab6 dNMzjyI9lMDn/zn/FS2DiUFQmJSgc54= 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-694-IAOxDKXBOjO7Co8RN5tQEQ-1; Thu, 12 Oct 2023 06:04:53 -0400 X-MC-Unique: IAOxDKXBOjO7Co8RN5tQEQ-1 Received: by mail-wm1-f72.google.com with SMTP id 5b1f17b1804b1-40590e6bd67so6621185e9.2 for ; Thu, 12 Oct 2023 03:04:52 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1697105092; x=1697709892; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=ZP3YKIE2yTvGXlDgaajxVTn+QoCcFUX8u0IfhHPvSSM=; b=TClnbL5X9bF5Jmqp7KnA3Q+MfZES+bNK5005NJE1k3Iv2F1UgOJEN9odVNoJYWpjWj 1rD3w9a9onAAptozLLmkrLveffn40gv4OvKxmbO6/nqoIpTjHfF5GsB9P1phe3h/ZU50 rS4JjZP5lxC17ofwpDAw8gu+CCa5kORimc6AjABUBezKnRVOnlZfi6HNzdsPsQpm5xcI 4DRdBtp16oRRXy3kc5eLPR8+i5iAU3t3almbaP6SwYcS+Oedz1vDynK4mrUcPpYvuTIb qhO/3nK+wffj6FIDoqxXfHNZz8u1Mw0q35Rpj21swfrxAnDkq7X95rFWcQGdx4MrBH0k mFcA== X-Gm-Message-State: AOJu0YyJjcAPQJDjSw7YMIlSBiPVtelw7WA0eK40dyv1zL/GJam636CG qtyXO2aAIejSw9s54hGy0Y5zrTE+3XK1aSYk5OoU1KE9IFKzjaH69wN1OXRmiuLaLLFRFzaidKA SQpt99xwGt7FHIdEdrTWrc4eQIjHujsBf6oHYjummEp1RoCUIRbhK1OMgMAX33IiISn0= X-Received: by 2002:a5d:5a06:0:b0:32d:23b7:49af with SMTP id bq6-20020a5d5a06000000b0032d23b749afmr8097983wrb.36.1697105091956; Thu, 12 Oct 2023 03:04:51 -0700 (PDT) X-Google-Smtp-Source: AGHT+IEFjUHJ0LjmLXXuMB//lnkXgG36QZt/dCCHplh833uIojeAlgX53dBp/Bpj3rnaqfowSG+Q0w== X-Received: by 2002:a5d:5a06:0:b0:32d:23b7:49af with SMTP id bq6-20020a5d5a06000000b0032d23b749afmr8097938wrb.36.1697105091228; Thu, 12 Oct 2023 03:04:51 -0700 (PDT) Received: from positronik4lide.redhat.com ([87.122.56.148]) by smtp.gmail.com with ESMTPSA id 4-20020a05600c228400b003fee53feab5sm19159780wmf.10.2023.10.12.03.04.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 12 Oct 2023 03:04:50 -0700 (PDT) From: jmeng@redhat.com To: dev@openvswitch.org Date: Thu, 12 Oct 2023 12:04:26 +0200 Message-Id: <20231012100425.14467-1-jmeng@redhat.com> X-Mailer: git-send-email 2.39.2 MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH] [RFC] Add options to output JSON from ovs-appctl commands. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Jakob Meng For monitoring systems such as Prometheus it would be beneficial if OVS and OVS-DPDK would expose statistics in a machine-readable format. Several approaches like UNIX socket, OVSDB queries and JSON output from ovs-xxx tools have been proposed [0],[1]. This proof of concept describes one way how ovs-xxx tools could output JSON in addition to plain-text for humans. With this patch, the 'ovs-appctl dpif/show' command has an optional [(-o|--output=)text|json] option. By default, the output format is plain-text as before. The JSON option provides the same amount of information but as JSON. The tests/pmd.at file has an example of how the output will look like. The new output option has been implemented for the dpif/show command specifically and not as a global option. This approach allows to add this option to commands incrementally. With a global option, each command would have to either announce which output format it supports and/or implement code to fail if the user-supplied format is unsupported. Also, passing global options to commands is not implemented atm. Both would require deeper changes than adding new options to command. One drawback of a command option is that we cannot (?) enforce the option name and values across all commands in code. We would have to ensure in reviews that the option is always called '-o/--output' and not '-f,--format' etc. Another issue is that most commands bring own code to parse argc/argv. This patch delegates the parsing code for the output option to a new function ovs_cmdl_parse_output_fmt() in lib/command-line.c. However, a more generic approach might be favorable to reduce duplicate argc/argv parsing code across commands. The existing getopt()/getopt_long() cannot be used in its current state because both have global state and both are already used to parse global options. We could modify both functions to use local state only or use alternatives [2]. Another question is whether JSON output should be pretty printed and sorted or not. In most cases it would be unnecessary because machines consume the output or humans could use jq for pretty printing. However, it would make tests more readable (for humans) without having to use jq (which would require us to introduce a dependency on jq). Wdyt? [0] https://bugzilla.redhat.com/show_bug.cgi?id=1824861#c7 [1] https://patchwork.ozlabs.org/project/openvswitch/patch/20230831131122.284913-1-rjarry@redhat.com/ [2] https://nullprogram.com/blog/2014/10/12/ Reported-at: https://bugzilla.redhat.com/1824861 Signed-off-by: Jakob Meng --- lib/command-line.c | 40 +++++++++++ lib/command-line.h | 8 +++ lib/unixctl.c | 76 +++++++++++++++------ lib/unixctl.h | 3 + ofproto/ofproto-dpif.c | 152 +++++++++++++++++++++++++++++++++++++---- tests/pmd.at | 29 ++++++++ 6 files changed, 273 insertions(+), 35 deletions(-) diff --git a/lib/command-line.c b/lib/command-line.c index 967f4f5d5..2da9f5fb1 100644 --- a/lib/command-line.c +++ b/lib/command-line.c @@ -53,6 +53,46 @@ ovs_cmdl_long_options_to_short_options(const struct option options[]) return xstrdup(short_options); } +static char * OVS_WARN_UNUSED_RESULT +ovs_cmdl_parse_output_fmt__(const char * arg, enum ovs_output_fmt *fmt) +{ + if (!strcmp(arg, "text")) { + *fmt = OVS_OUTPUT_FMT_TEXT; + } else if (!strcmp(arg, "json")) { + *fmt = OVS_OUTPUT_FMT_JSON; + } else { + return xasprintf("unsupported output format '%s'", arg); + } + return NULL; +} + +char * OVS_WARN_UNUSED_RESULT +ovs_cmdl_parse_output_fmt(int argi, int argc, const char *argv[], + enum ovs_output_fmt *fmt, int *parsed) +{ + ovs_assert(argi < argc); + const char *arg = argv[argi]; + *parsed = 0; + + if (!strncmp(arg, "--output=", 9)) { + *parsed = 2; + if (strlen(arg) <= 9) { + return xasprintf("option '%s' requires an argument", arg); + } + return ovs_cmdl_parse_output_fmt__(arg+9, fmt); + } else if (!strcmp(arg, "-o") || !strcmp(arg, "--output")) { + *parsed = 2; + if (argi+1 >= argc) { + return xasprintf("option '%s' requires an argument", arg); + } + return ovs_cmdl_parse_output_fmt__(argv[argi+1], fmt); + } else { + return NULL; + } + + OVS_NOT_REACHED(); +} + static char * OVS_WARN_UNUSED_RESULT build_short_options(const struct option *long_options) { diff --git a/lib/command-line.h b/lib/command-line.h index fc2a2690f..d04377098 100644 --- a/lib/command-line.h +++ b/lib/command-line.h @@ -46,6 +46,14 @@ struct ovs_cmdl_command { char *ovs_cmdl_long_options_to_short_options(const struct option *options); +enum ovs_output_fmt { + OVS_OUTPUT_FMT_TEXT, + OVS_OUTPUT_FMT_JSON +}; + +char * ovs_cmdl_parse_output_fmt(int argi, int argc, const char *argv[], + enum ovs_output_fmt *fmt, int *parsed); + struct ovs_cmdl_parsed_option { const struct option *o; char *arg; diff --git a/lib/unixctl.c b/lib/unixctl.c index 103357ee9..b83429e3d 100644 --- a/lib/unixctl.c +++ b/lib/unixctl.c @@ -21,7 +21,6 @@ #include "coverage.h" #include "dirs.h" #include "openvswitch/dynamic-string.h" -#include "openvswitch/json.h" #include "jsonrpc.h" #include "openvswitch/list.h" #include "openvswitch/poll-loop.h" @@ -128,36 +127,41 @@ unixctl_command_register(const char *name, const char *usage, shash_add(&commands, name, command); } -static void -unixctl_command_reply__(struct unixctl_conn *conn, - bool success, const char *body) +static struct json * +json_string_create__(const char *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)); + return json_string_create_nocopy(xasprintf("%s\n", body)); } else { - body_json = json_string_create(body); + return json_string_create(body); } +} + +static void +unixctl_command_reply__(struct unixctl_conn *conn, + bool success, struct json *body) +{ + struct jsonrpc_msg *reply; + + COVERAGE_INC(unixctl_replied); + ovs_assert(conn->request_id); 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, 0); VLOG_DBG("replying with %s, id=%s: \"%s\"", - success ? "success" : "error", id, body); + success ? "success" : "error", id, msg); + free(msg); free(id); } @@ -170,22 +174,36 @@ 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. */ + * unixctl_command_reply(), unixctl_command_reply_error() or + * unixctl_command_reply_json() may be made per request. */ void unixctl_command_reply(struct unixctl_conn *conn, const char *result) { - unixctl_command_reply__(conn, true, result); + struct json *body = json_string_create__(result); + unixctl_command_reply__(conn, true, body); + json_destroy(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. Only one call + * to unixctl_command_reply(), unixctl_command_reply_error() or + * unixctl_command_reply_json() may be made per request. */ void unixctl_command_reply_error(struct unixctl_conn *conn, const char *error) { - unixctl_command_reply__(conn, false, error); + struct json *body = json_string_create__(error); + unixctl_command_reply__(conn, false, body); + json_destroy(body); +} + +/* 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(), unixctl_command_reply_error() or + * unixctl_command_reply_json() may be made per request. */ +void +unixctl_command_reply_json(struct unixctl_conn *conn, struct json *body) +{ + unixctl_command_reply__(conn, true, body); } /* Creates a unixctl server listening on 'path', which for POSIX may be: @@ -518,6 +536,20 @@ unixctl_client_transact(struct jsonrpc *client, const char *command, int argc, } else if (reply->result) { if (reply->result->type == JSON_STRING) { *result = xstrdup(json_string(reply->result)); + } else if (reply->result->type == JSON_OBJECT || + reply->result->type == JSON_ARRAY) { + /* TODO: How about other result types? */ + + /* TODO: Do we really want to prettyfy and sort the output? + * The benefit for users is probably minimal because they could + * simply use jq to format the output if needed. Since JSON output + * is meant to be consumed by machines, this pretty-printing is + * probably unnecessary in most cases. + * However, it might have its use in our unit tests because it + * allows us to make readable checks without having to introduce a + * dependency on jq. + */ + *result = json_to_string(reply->result, JSSF_PRETTY | JSSF_SORT); } else { VLOG_WARN("%s: unexpected result type in JSON rpc reply: %s", jsonrpc_get_name(client), diff --git a/lib/unixctl.h b/lib/unixctl.h index 4562dbc49..87c45fa14 100644 --- a/lib/unixctl.h +++ b/lib/unixctl.h @@ -17,6 +17,8 @@ #ifndef UNIXCTL_H #define UNIXCTL_H 1 +#include "openvswitch/json.h" + #ifdef __cplusplus extern "C" { #endif @@ -47,6 +49,7 @@ void unixctl_command_register(const char *name, const char *usage, unixctl_cb_func *cb, void *aux); 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/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index ba5706f6a..27d617a8f 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -20,6 +20,7 @@ #include "bond.h" #include "bundle.h" #include "byte-order.h" +#include "command-line.h" #include "connectivity.h" #include "connmgr.h" #include "coverage.h" @@ -6344,8 +6345,101 @@ done: return changed; } +static struct json * +dpif_show_backer_json(const struct dpif_backer *backer) +{ + struct json *json_backer = json_object_create(); + + /* name */ + json_object_put_string(json_backer, "name", dpif_name(backer->dpif)); + + /* stats */ + struct dpif_dp_stats dp_stats; + dpif_get_dp_stats(backer->dpif, &dp_stats); + struct json *json_dp_stats = json_object_create(); + json_object_put_format(json_dp_stats, "n_hit", "%"PRIu64, dp_stats.n_hit); + json_object_put_format(json_dp_stats, "n_missed", "%"PRIu64, + dp_stats.n_missed); + json_object_put(json_backer, "stats", json_dp_stats); + + /* ofprotos */ + struct json *json_ofprotos = json_array_create_empty(); + struct shash ofproto_shash; + shash_init(&ofproto_shash); + const struct shash_node **ofprotos = get_ofprotos(&ofproto_shash); + for (size_t i = 0; i < shash_count(&ofproto_shash); i++) { + struct ofproto_dpif *ofproto = ofprotos[i]->data; + + if (ofproto->backer != backer) { + continue; + } + + struct json *json_ofproto = json_object_create(); + + /* ofproto name */ + json_object_put_string(json_ofproto, "name", ofproto->up.name); + + /* ofproto ports */ + struct json *json_ofproto_ports = json_array_create_empty(); + const struct shash_node **ports = shash_sort(&ofproto->up.port_by_name); + for (size_t j = 0; j < shash_count(&ofproto->up.port_by_name); j++) { + const struct shash_node *port = ports[j]; + struct ofport *ofport = port->data; + + struct json * json_ofproto_port = json_object_create(); + /* ofproto port netdev name */ + json_object_put_string(json_ofproto_port, "netdev_name", + netdev_get_name(ofport->netdev)); + /* ofproto port ofp port */ + json_object_put_format(json_ofproto_port, "ofp_port", "%u", + ofport->ofp_port); + + /* ofproto port odp port */ + 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, "odp_port", + "%"PRIu32, odp_port); + } else { + json_object_put_string(json_ofproto_port, "odp_port", "none"); + } + + /* ofproto port netdev type */ + json_object_put_string(json_ofproto_port, "netdev_type", + netdev_get_type(ofport->netdev)); + + /* ofproto port config */ + struct json *json_port_config = json_object_create(); + struct smap config; + smap_init(&config); + if (!netdev_get_config(ofport->netdev, &config)) { + struct smap_node *node; + SMAP_FOR_EACH(node, &config) { + json_object_put_string(json_port_config, node->key, + node->value); + } + } + smap_destroy(&config); + json_object_put(json_ofproto_port, "netdev_config", + json_port_config); + + json_array_add(json_ofproto_ports, json_ofproto_port); + } /* ofproto port */ + + free(ports); + json_object_put(json_ofproto, "ports", json_ofproto_ports); + + json_array_add(json_ofprotos, json_ofproto); + } /* ofproto */ + shash_destroy(&ofproto_shash); + free(ofprotos); + + json_object_put(json_backer, "ofprotos", json_ofprotos); + return json_backer; +} + static void -dpif_show_backer(const struct dpif_backer *backer, struct ds *ds) +dpif_show_backer_text(const struct dpif_backer *backer, struct ds *ds) { const struct shash_node **ofprotos; struct dpif_dp_stats dp_stats; @@ -6409,21 +6503,53 @@ dpif_show_backer(const struct dpif_backer *backer, struct ds *ds) } static void -ofproto_unixctl_dpif_show(struct unixctl_conn *conn, int argc OVS_UNUSED, - const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) +ofproto_unixctl_dpif_show(struct unixctl_conn *conn, int argc, + const char *argv[], void *aux OVS_UNUSED) { struct ds ds = DS_EMPTY_INITIALIZER; - const struct shash_node **backers; - int i; + enum ovs_output_fmt fmt = OVS_OUTPUT_FMT_TEXT; - backers = shash_sort(&all_dpif_backers); - for (i = 0; i < shash_count(&all_dpif_backers); i++) { - dpif_show_backer(backers[i]->data, &ds); + /* Parse options. Both getopt() and getopt_long() cannot be used here + * because they use global variables and are already used by the caller. + */ + /* TODO: Modify getopt()/getopt_long() to use local storage or use + * alternatives like presented in + * https://nullprogram.com/blog/2014/10/12/ ? */ + for (int i = 1, parsed = 0; i < argc; i += parsed) { + char *error = ovs_cmdl_parse_output_fmt(i, argc, argv, &fmt, &parsed); + + if (error) { + unixctl_command_reply_error(conn, error); + free(error); + return; + } + + if (!parsed) { + ds_put_format(&ds, "Unrecognized option %s", argv[i]); + unixctl_command_reply_error(conn, ds_cstr(&ds)); + ds_destroy(&ds); + return; + } } - free(backers); - unixctl_command_reply(conn, ds_cstr(&ds)); - ds_destroy(&ds); + if (fmt == OVS_OUTPUT_FMT_JSON) { + struct json *backers = json_array_create_empty(); + const struct shash_node *backer; + SHASH_FOR_EACH(backer, &all_dpif_backers) { + json_array_add(backers, dpif_show_backer_json(backer->data)); + } + unixctl_command_reply_json(conn, backers); + json_destroy(backers); + } else { + const struct shash_node **backers = shash_sort(&all_dpif_backers); + for (int i = 0; i < shash_count(&all_dpif_backers); i++) { + dpif_show_backer_text(backers[i]->data, &ds); + } + free(backers); + + unixctl_command_reply(conn, ds_cstr(&ds)); + ds_destroy(&ds); + } } static void @@ -6595,8 +6721,8 @@ ofproto_unixctl_init(void) ofproto_unixctl_mcast_snooping_show, NULL); unixctl_command_register("dpif/dump-dps", "", 0, 0, ofproto_unixctl_dpif_dump_dps, NULL); - unixctl_command_register("dpif/show", "", 0, 0, ofproto_unixctl_dpif_show, - NULL); + unixctl_command_register("dpif/show", "[(-o|--output=)text|json]", 0, 2, + ofproto_unixctl_dpif_show, NULL); unixctl_command_register("dpif/show-dp-features", "bridge", 1, 1, ofproto_unixctl_dpif_show_dp_features, NULL); unixctl_command_register("dpif/dump-flows", diff --git a/tests/pmd.at b/tests/pmd.at index 7bdaca9e7..154fd48a3 100644 --- a/tests/pmd.at +++ b/tests/pmd.at @@ -100,6 +100,35 @@ dummy@ovs-dummy: hit:0 missed:0 p0 1/1: (dummy-pmd: configured_rx_queues=1, configured_tx_queues=, requested_rx_queues=1, requested_tx_queues=) ]) +AT_CHECK([ovs-appctl dpif/show --output json | sed 's/\(tx_queues": "\)[[0-9]]*"/\1"/g'], [0], [dnl +[[ + { + "name": "dummy@ovs-dummy", + "ofprotos": [ + { + "name": "br0", + "ports": [ + { + "netdev_config": { + }, + "netdev_name": "br0", + "netdev_type": "dummy-internal", + "odp_port": "100", + "ofp_port": "65534"}, + { + "netdev_config": { + "configured_rx_queues": "1", + "configured_tx_queues": "", + "requested_rx_queues": "1", + "requested_tx_queues": ""}, + "netdev_name": "p0", + "netdev_type": "dummy-pmd", + "odp_port": "1", + "ofp_port": "1"}]}], + "stats": { + "n_hit": "0", + "n_missed": "0"}}]]]) + OVS_VSWITCHD_STOP AT_CLEANUP