diff mbox series

[ovs-dev,v4,1/6] Add global option for JSON output to ovs-appctl.

Message ID 20231116104120.25676-2-jmeng@redhat.com
State Changes Requested
Headers show
Series Add global option to output JSON from ovs-appctl cmds. | expand

Checks

Context Check Description
ovsrobot/apply-robot warning apply and check: warning
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/intel-ovs-compilation success test: success

Commit Message

Jakob Meng Nov. 16, 2023, 10:41 a.m. UTC
From: Jakob Meng <code@jakobmeng.de>

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-xxx
tools. They gain a global option '-f,--format' which allows users to
request JSON instead of plain-text for humans. An example call
implemented in a later patch is 'ovs-appctl --format json dpif/show'.

For that it is necessary to change the JSON-RPC API lib/unixctl.*
which ovs-xxx tools use to communicate with ovs-vswitchd across unix
sockets. It now allows to transport the requested output format
besides the command and its args. This change 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.

Previously, the command was copied to the 'method' parameter in
JSON-RPC while the args were copied to the 'params' parameter. Without
any other means to transport parameters via JSON-RPC left, the meaning
of 'method' and 'params' had to be changed: 'method' will now be
'execute/v1' when an output format other than 'text' is requested. In
that case, the first parameter of the JSON array 'params' will now be
the designated command, the second one the output format and the rest
will be command args.

unixctl_command_register() in lib/unixctl.* has been cloned as
unixctl_command_register_fmt() in order to demonstrate the new API.
The latter will be replaced with the former in a later patch. The
new function gained an int argument called 'output_fmts' with which
commands have to announce their support for output formats.

Function type unixctl_cb_func in lib/unixctl.h has gained a new arg
'enum ovs_output_fmt fmt'. For now, it has been added as a comment
only for the huge changes reason mentioned earlier. The output format
which is passed via unix socket to ovs-vswitchd will be converted into
a value of type 'enum ovs_output_fmt' in lib/unixctl.c and then passed
to said 'fmt' arg of the choosen command handler (in a future patch).
When a requested output format is not supported by a command,
then process_command() in lib/unixctl.c will return an error.

This patch does not yet pass the choosen output format to commands.
Doing so requires changes to all unixctl_command_register() calls and
all command callbacks. To improve readibility those changes have been
split out into a follow up patch. Respectively, whenever an output
format other than 'text' is choosen for ovs-xxx tools, they will fail.
By default, the output format is plain-text as before.

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 alines with ovsdb-client
where '-f,--format' controls output formatting.

Reported-at: https://bugzilla.redhat.com/1824861
Signed-off-by: Jakob Meng <code@jakobmeng.de>
---
 NEWS                   |   2 +
 lib/command-line.c     |  36 ++++++
 lib/command-line.h     |  10 ++
 lib/dpctl.h            |   4 +
 lib/unixctl.c          | 249 +++++++++++++++++++++++++++++++----------
 lib/unixctl.h          |  17 ++-
 tests/pmd.at           |   5 +
 utilities/ovs-appctl.c |  67 ++++++++---
 utilities/ovs-dpctl.c  |   1 +
 9 files changed, 319 insertions(+), 72 deletions(-)

Comments

0-day Robot Nov. 16, 2023, 10:59 a.m. UTC | #1
Bleep bloop.  Greetings Jakob Meng, I am a robot and I have tried out your patch.
Thanks for your contribution.

I encountered some error that I wasn't expecting.  See the details below.


checkpatch:
WARNING: Line lacks whitespace around operator
WARNING: Line lacks whitespace around operator
#701 FILE: utilities/ovs-appctl.c:114:
  -f, --format=FMT   Output format. One of: 'json', or 'text'\n\

Lines checked: 808, Warnings: 2, Errors: 0


Please check this out.  If you feel there has been an error, please email aconole@redhat.com

Thanks,
0-day Robot
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 43aea97b5..12a895629 100644
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,8 @@  Post-v3.2.0
        a.k.a. 'configured' values, can be found in the 'status' column of
        the Interface table, i.e. with 'ovs-vsctl get interface <..> status'.
        Reported names adjusted accordingly.
+     * Added new option [-f|--format] to choose the output format, e.g. 'json'
+       or 'text' (by default).
 
 
 v3.2.0 - 17 Aug 2023
diff --git a/lib/command-line.c b/lib/command-line.c
index 967f4f5d5..775e0430a 100644
--- a/lib/command-line.c
+++ b/lib/command-line.c
@@ -24,6 +24,7 @@ 
 #include "ovs-thread.h"
 #include "util.h"
 #include "openvswitch/vlog.h"
+#include "openvswitch/json.h"
 
 VLOG_DEFINE_THIS_MODULE(command_line);
 
@@ -53,6 +54,41 @@  ovs_cmdl_long_options_to_short_options(const struct option options[])
     return xstrdup(short_options);
 }
 
+const char *
+ovs_output_fmt_to_string(enum ovs_output_fmt fmt)
+{
+    switch (fmt) {
+    case OVS_OUTPUT_FMT_TEXT:
+        return "text";
+
+    case OVS_OUTPUT_FMT_JSON:
+        return "json";
+
+    default:
+        return NULL;
+    }
+}
+
+struct json *
+ovs_output_fmt_to_json(enum ovs_output_fmt fmt)
+{
+    const char *string = ovs_output_fmt_to_string(fmt);
+    return string ? json_string_create(string) : NULL;
+}
+
+bool
+ovs_output_fmt_from_string(const char *string, enum ovs_output_fmt *fmt)
+{
+    if (!strcmp(string, "text")) {
+        *fmt = OVS_OUTPUT_FMT_TEXT;
+    } else if (!strcmp(string, "json")) {
+        *fmt = OVS_OUTPUT_FMT_JSON;
+    } else {
+        return false;
+    }
+    return true;
+}
+
 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..494274cec 100644
--- a/lib/command-line.h
+++ b/lib/command-line.h
@@ -20,6 +20,7 @@ 
 /* Utilities for command-line parsing. */
 
 #include "compiler.h"
+#include <openvswitch/list.h>
 
 struct option;
 
@@ -46,6 +47,15 @@  struct ovs_cmdl_command {
 
 char *ovs_cmdl_long_options_to_short_options(const struct option *options);
 
+enum ovs_output_fmt {
+    OVS_OUTPUT_FMT_TEXT = 1 << 0,
+    OVS_OUTPUT_FMT_JSON = 1 << 1
+};
+
+const char *ovs_output_fmt_to_string(enum ovs_output_fmt);
+struct json *ovs_output_fmt_to_json(enum ovs_output_fmt);
+bool ovs_output_fmt_from_string(const char *, enum ovs_output_fmt *);
+
 struct ovs_cmdl_parsed_option {
     const struct option *o;
     char *arg;
diff --git a/lib/dpctl.h b/lib/dpctl.h
index 9d0052152..1c9b2409f 100644
--- a/lib/dpctl.h
+++ b/lib/dpctl.h
@@ -19,6 +19,7 @@ 
 #include <stdbool.h>
 
 #include "compiler.h"
+#include "command-line.h"
 
 struct dpctl_params {
     /* True if it is called by ovs-appctl command. */
@@ -42,6 +43,9 @@  struct dpctl_params {
     /* --names: Use port names in output? */
     bool names;
 
+    /* Output format. */
+    enum ovs_output_fmt format;
+
     /* Callback for printing.  This function is called from dpctl_run_command()
      * to output data.  The 'aux' parameter is set to the 'aux'
      * member.  The 'error' parameter is true if 'string' is an error
diff --git a/lib/unixctl.c b/lib/unixctl.c
index 103357ee9..5de59f339 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"
@@ -39,6 +38,7 @@  COVERAGE_DEFINE(unixctl_replied);
 struct unixctl_command {
     const char *usage;
     int min_args, max_args;
+    int output_fmts;
     unixctl_cb_func *cb;
     void *aux;
 };
@@ -63,6 +63,8 @@  static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
 
 static struct shash commands = SHASH_INITIALIZER(&commands);
 
+static const char *rpc_marker = "execute/v1";
+
 static void
 unixctl_list_commands(struct unixctl_conn *conn, int argc OVS_UNUSED,
                       const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
@@ -109,6 +111,28 @@  void
 unixctl_command_register(const char *name, const char *usage,
                          int min_args, int max_args,
                          unixctl_cb_func *cb, void *aux)
+{
+    unixctl_command_register_fmt(name, usage, min_args, max_args,
+                                 OVS_OUTPUT_FMT_TEXT, cb, aux);
+}
+
+/* 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.)
+ * 'output_fmts' is a bitmap that defines what output formats a command
+ * supports, e.g. OVS_OUTPUT_FMT_TEXT | OVS_OUTPUT_FMT_JSON.
+ *
+ * 'cb' is called when the command is received.  It is passed an array
+ * containing the command name and arguments, plus a copy of 'aux'.  Normally
+ * 'cb' should reply by calling unixctl_command_reply() or
+ * unixctl_command_reply_error() before it returns, but if the command cannot
+ * be handled immediately then it can defer the reply until later.  A given
+ * connection can only process a single request at a time, so a reply must be
+ * made eventually to avoid blocking that connection. */
+void
+unixctl_command_register_fmt(const char *name, const char *usage,
+                             int min_args, int max_args, int output_fmts,
+                             unixctl_cb_func *cb, void *aux)
 {
     struct unixctl_command *command;
     struct unixctl_command *lookup = shash_find_data(&commands, name);
@@ -123,41 +147,48 @@  unixctl_command_register(const char *name, const char *usage,
     command->usage = usage;
     command->min_args = min_args;
     command->max_args = max_args;
+    command->output_fmts = output_fmts;
     command->cb = cb;
     command->aux = aux;
     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 +201,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:
@@ -266,6 +309,11 @@  process_command(struct unixctl_conn *conn, struct jsonrpc_msg *request)
 
     struct unixctl_command *command;
     struct json_array *params;
+    const char *method;
+    enum ovs_output_fmt fmt;
+    struct svec argv = SVEC_EMPTY_INITIALIZER;
+    int args_offset;
+    bool plain_rpc;
 
     COVERAGE_INC(unixctl_received);
     conn->request_id = json_clone(request->id);
@@ -279,45 +327,95 @@  process_command(struct unixctl_conn *conn, struct jsonrpc_msg *request)
         free(id_s);
     }
 
+    /* The JSON-RPC API requires an indirection in order to allow transporting
+     * additional data like the output format besides command and args.  For
+     * backward compatibility with older clients the plain RPC is still
+     * supported. */
+    plain_rpc = strcmp(request->method, rpc_marker);
+    args_offset = plain_rpc ? 0 : 2;
+
     params = json_array(request->params);
-    command = shash_find_data(&commands, request->method);
+    if (!plain_rpc && (params->n < 2)) {
+        error = xasprintf("JSON-RPC API mismatch: Unexpected # of params:"\
+                          " %"PRIuSIZE, params->n);
+        goto error;
+    }
+
+    for (size_t i = 0; i < params->n; i++) {
+        if (params->elems[i]->type != JSON_STRING) {
+            error = xasprintf("command has non-string argument: %s",
+                              json_to_string(params->elems[i], 0));
+            goto error;
+        }
+    }
+
+    /* Extract method name. */
+    method = plain_rpc ? request->method : json_string(params->elems[0]);
+
+    /* Extract output format. */
+    if (plain_rpc) {
+        fmt = OVS_OUTPUT_FMT_TEXT;
+    } else {
+        if (!ovs_output_fmt_from_string(json_string(params->elems[1]), &fmt)) {
+            error = xasprintf("invalid output format: %s",
+                              json_string(params->elems[1]));
+            goto error;
+        }
+    }
+
+    /* Find command with method name. */
+    command = shash_find_data(&commands, method);
+
+    /* Verify that method call complies with command requirements. */
     if (!command) {
         error = xasprintf("\"%s\" is not a valid command (use "
                           "\"list-commands\" to see a list of valid commands)",
-                          request->method);
-    } else if (params->n < command->min_args) {
+                          method);
+        goto error;
+    } else if ((params->n - args_offset) < command->min_args) {
         error = xasprintf("\"%s\" command requires at least %d arguments",
-                          request->method, command->min_args);
-    } else if (params->n > command->max_args) {
+                          method, command->min_args);
+        goto error;
+    } else if ((params->n - args_offset) > command->max_args) {
         error = xasprintf("\"%s\" command takes at most %d arguments",
-                          request->method, command->max_args);
-    } else {
-        struct svec argv = SVEC_EMPTY_INITIALIZER;
-        int  i;
-
-        svec_add(&argv, request->method);
-        for (i = 0; i < params->n; i++) {
-            if (params->elems[i]->type != JSON_STRING) {
-                error = xasprintf("\"%s\" command has non-string argument",
-                                  request->method);
-                break;
-            }
-            svec_add(&argv, json_string(params->elems[i]));
-        }
-        svec_terminate(&argv);
-
-        if (!error) {
-            command->cb(conn, argv.n, (const char **) argv.names,
-                        command->aux);
-        }
+                          method, command->max_args);
+        goto error;
+    } else if ((!command->output_fmts && fmt != OVS_OUTPUT_FMT_TEXT) ||
+               (command->output_fmts && !(fmt & command->output_fmts)))
+    {
+        error = xasprintf("\"%s\" command does not support output format"\
+                          " \"%s\" %d %d", method,
+                          ovs_output_fmt_to_string(fmt), command->output_fmts,
+                          fmt);
+        goto error;
+    }
 
-        svec_destroy(&argv);
+    /* FIXME: Remove this check once output format will be passed to the
+     *        command handler below. */
+    if (fmt != OVS_OUTPUT_FMT_TEXT) {
+        error = xasprintf("output format \"%s\" has not been implemented yet",
+                          ovs_output_fmt_to_string(fmt));
+        goto error;
     }
 
-    if (error) {
-        unixctl_command_reply_error(conn, error);
-        free(error);
+    /* Extract command args. */
+    svec_add(&argv, method);
+    for (size_t i = args_offset; i < params->n; i++) {
+        svec_add(&argv, json_string(params->elems[i]));
     }
+    svec_terminate(&argv);
+
+    /* FIXME: Output format will be passed as 'fmt' to the command in later
+     *        patch. */
+    command->cb(conn, argv.n, (const char **) argv.names, /* fmt, */
+                command->aux);
+
+    svec_destroy(&argv);
+
+    return;
+error:
+    unixctl_command_reply_error(conn, error);
+    free(error);
 }
 
 static int
@@ -483,21 +581,43 @@  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[], enum ovs_output_fmt fmt,
+                        char **result, char **err)
 {
     struct jsonrpc_msg *request, *reply;
     struct json **json_args, *params;
     int error, i;
+    /* The JSON-RPC API requires an indirection in order to allow transporting
+     * additional data like the output format besides command and args.  For
+     * backward compatibility with older servers the plain RPC is still
+     * supported. */
+    bool plain_rpc = (fmt == OVS_OUTPUT_FMT_TEXT);
 
     *result = NULL;
     *err = NULL;
 
-    json_args = xmalloc(argc * sizeof *json_args);
-    for (i = 0; i < argc; i++) {
-        json_args[i] = json_string_create(argv[i]);
+    if (plain_rpc) {
+        json_args = xmalloc(argc * sizeof *json_args);
+        for (i = 0; i < argc; i++) {
+            json_args[i] = json_string_create(argv[i]);
+        }
+
+        params = json_array_create(json_args, argc);
+        request = jsonrpc_create_request(command, params, NULL);
+    } else {
+        json_args = xmalloc((argc + 2) * sizeof *json_args);
+        json_args[0] = json_string_create(command);
+        json_args[1] = ovs_output_fmt_to_json(fmt);
+        for (i = 0; i < argc; i++) {
+            json_args[i + 2] = json_string_create(argv[i]);
+        }
+
+        params = json_array_create(json_args, argc + 2);
+
+        /* Use a versioned command to ensure that both server and client
+         * use the same JSON-RPC API. */
+        request = jsonrpc_create_request(rpc_marker, params, NULL);
     }
-    params = json_array_create(json_args, argc);
-    request = jsonrpc_create_request(command, params, NULL);
 
     error = jsonrpc_transact_block(client, request, &reply);
     if (error) {
@@ -508,7 +628,17 @@  unixctl_client_transact(struct jsonrpc *client, const char *command, int argc,
 
     if (reply->error) {
         if (reply->error->type == JSON_STRING) {
-            *err = xstrdup(json_string(reply->error));
+            /* Catch incompatible server and return helpful error msg. */
+            char *plain_rpc_error = xasprintf("\"%s\" is not a valid command",
+                                              rpc_marker);
+            if (!strncmp(plain_rpc_error, json_string(reply->error),
+                         strlen(plain_rpc_error))) {
+                *err = xstrdup("JSON RPC reply indicates incompatible server. "
+                               "Please upgrade server side to newer version.");
+            } else {
+                *err = xstrdup(json_string(reply->error));
+            }
+            free(plain_rpc_error);
         } else {
             VLOG_WARN("%s: unexpected error type in JSON RPC reply: %s",
                       jsonrpc_get_name(client),
@@ -518,6 +648,9 @@  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) {
+            *result = json_to_string(reply->result, 0);
         } 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..896ecb062 100644
--- a/lib/unixctl.h
+++ b/lib/unixctl.h
@@ -17,6 +17,9 @@ 
 #ifndef UNIXCTL_H
 #define UNIXCTL_H 1
 
+#include "openvswitch/json.h"
+#include "command-line.h"
+
 #ifdef  __cplusplus
 extern "C" {
 #endif
@@ -36,17 +39,29 @@  int unixctl_client_create(const char *path, struct jsonrpc **client);
 int unixctl_client_transact(struct jsonrpc *client,
                             const char *command,
                             int argc, char *argv[],
+                            enum ovs_output_fmt fmt,
                             char **result, char **error);
 
 /* Command registration. */
 struct unixctl_conn;
+/* FIXME: Output format will be passed as 'fmt' to the command in later patch.
+ */
 typedef void unixctl_cb_func(struct unixctl_conn *,
-                             int argc, const char *argv[], void *aux);
+                             int argc, const char *argv[],
+                             /* enum ovs_output_fmt fmt, */ void *aux);
+/* FIXME: unixctl_command_register() will be replaced with
+ *        unixctl_command_register_fmt() in a later patch of this series.  It
+ *        is kept temporarily to reduce the amount of changes in this patch. */
 void unixctl_command_register(const char *name, const char *usage,
                               int min_args, int max_args,
                               unixctl_cb_func *cb, void *aux);
+void unixctl_command_register_fmt(const char *name, const char *usage,
+                                  int min_args, int max_args, int output_fmts,
+                                  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/tests/pmd.at b/tests/pmd.at
index 06cc90477..3fcda13fd 100644
--- a/tests/pmd.at
+++ b/tests/pmd.at
@@ -93,6 +93,11 @@  pmd thread numa_id <cleared> core_id <cleared>:
   overhead: NOT AVAIL
 ])
 
+AT_CHECK([ovs-appctl --format json dpif-netdev/pmd-rxq-show], [2], [], [dnl
+"dpif-netdev/pmd-rxq-show" command does not support output format "json" 1 2
+ovs-appctl: ovs-vswitchd: server returned an error
+])
+
 AT_CHECK([ovs-appctl dpif/show], [0], [dnl
 dummy@ovs-dummy: hit:0 missed:0
   br0:
diff --git a/utilities/ovs-appctl.c b/utilities/ovs-appctl.c
index ba0c172e6..feff1be0b 100644
--- a/utilities/ovs-appctl.c
+++ b/utilities/ovs-appctl.c
@@ -34,7 +34,16 @@ 
 #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 ovs_output_fmt format;
+    char *target;
+};
+
+static struct cmdl_args *cmdl_args_create(void);
+static void cmdl_args_destroy(struct cmdl_args *);
+static struct cmdl_args *parse_command_line(int argc, char *argv[]);
 static struct jsonrpc *connect_to_target(const char *target);
 
 int
@@ -43,30 +52,30 @@  main(int argc, char *argv[])
     char *cmd_result, *cmd_error;
     struct jsonrpc *client;
     char *cmd, **cmd_argv;
-    const char *target;
+    struct cmdl_args *args;
     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. */
     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);
+                                    args->format, &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);
+        ovs_error(0, "%s: server returned an error", args->target);
         exit(2);
     } else if (cmd_result) {
         fputs(cmd_result, stdout);
@@ -74,6 +83,7 @@  main(int argc, char *argv[])
         OVS_NOT_REACHED();
     }
 
+    cmdl_args_destroy(args);
     jsonrpc_close(client);
     free(cmd_result);
     free(cmd_error);
@@ -101,13 +111,34 @@  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\
+                     ('text', by default)\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 = OVS_OUTPUT_FMT_TEXT;
+    args->target = NULL;
+
+    return args;
+}
+
+static void
+cmdl_args_destroy(struct cmdl_args *args) {
+    if (args->target) {
+        free(args->target);
+    }
+
+    free(args);
+}
+
+static struct cmdl_args *
 parse_command_line(int argc, char *argv[])
 {
     enum {
@@ -117,6 +148,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 +158,11 @@  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;
+
+    struct cmdl_args *args = cmdl_args_create();
     int e_options;
     unsigned int timeout = 0;
 
-    target = NULL;
     e_options = 0;
     for (;;) {
         int option;
@@ -141,10 +173,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 = xstrdup(optarg);
             break;
 
         case 'e':
@@ -157,6 +189,12 @@  parse_command_line(int argc, char *argv[])
             }
             break;
 
+        case 'f':
+            if (!ovs_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 +232,10 @@  parse_command_line(int argc, char *argv[])
                   "(use --help for help)");
     }
 
-    return target ? target : "ovs-vswitchd";
+    if (!args->target) {
+        args->target = xstrdup("ovs-vswitchd");
+    }
+    return args;
 }
 
 static struct jsonrpc *
diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
index 56d7a942b..30104ddb2 100644
--- a/utilities/ovs-dpctl.c
+++ b/utilities/ovs-dpctl.c
@@ -63,6 +63,7 @@  main(int argc, char *argv[])
     fatal_ignore_sigpipe();
 
     dpctl_p.is_appctl = false;
+    dpctl_p.format = OVS_OUTPUT_FMT_TEXT;
     dpctl_p.output = dpctl_print;
     dpctl_p.usage = usage;