@@ -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)
{
@@ -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;
@@ -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,42 @@ 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);
}
+}
+
+/* Takes ownership of '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 +175,34 @@ 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);
+ unixctl_command_reply__(conn, true, json_string_create__(result));
}
/* 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);
+ unixctl_command_reply__(conn, false, json_string_create__(error));
+}
+
+/* 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.
+ *
+ * Takes ownership of 'body'. */
+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 +535,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),
@@ -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
}
@@ -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,103 @@ 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;
+ 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 +6505,52 @@ 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;
+
+ /* 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);
- backers = shash_sort(&all_dpif_backers);
- for (i = 0; i < shash_count(&all_dpif_backers); i++) {
- dpif_show_backer(backers[i]->data, &ds);
+ 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);
+ } 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 +6722,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",
@@ -100,6 +100,35 @@ dummy@ovs-dummy: hit:0 missed:0
p0 1/1: (dummy-pmd: configured_rx_queues=1, configured_tx_queues=<cleared>, requested_rx_queues=1, requested_tx_queues=<cleared>)
])
+AT_CHECK([ovs-appctl dpif/show --output json | sed 's/\(tx_queues": "\)[[0-9]]*"/\1<cleared>"/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": "<cleared>",
+ "requested_rx_queues": "1",
+ "requested_tx_queues": "<cleared>"},
+ "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