diff mbox series

[ovs-dev,v7,5/6] appctl: Add option '--pretty' for pretty-printing JSON output.

Message ID 20240118152657.2816536-6-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 success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_Build_and_Test success github build: passed

Commit Message

Jakob Meng Jan. 18, 2024, 3:26 p.m. UTC
From: Jakob Meng <code@jakobmeng.de>

Signed-off-by: Jakob Meng <code@jakobmeng.de>
---
 NEWS                   |  3 +++
 lib/unixctl.c          |  4 ++--
 lib/unixctl.h          |  1 +
 tests/pmd.at           | 29 +++++++++++++++++++++++++++--
 utilities/ovs-appctl.c | 22 +++++++++++++++++++---
 5 files changed, 52 insertions(+), 7 deletions(-)

Comments

Eelco Chaudron March 15, 2024, 10:27 a.m. UTC | #1
On 18 Jan 2024, at 16:26, jmeng@redhat.com wrote:

> From: Jakob Meng <code@jakobmeng.de>
>
> Signed-off-by: Jakob Meng <code@jakobmeng.de>

Some comments below and you might want to add a commit message to this patch.

//Eelco

> ---
>  NEWS                   |  3 +++
>  lib/unixctl.c          |  4 ++--
>  lib/unixctl.h          |  1 +
>  tests/pmd.at           | 29 +++++++++++++++++++++++++++--
>  utilities/ovs-appctl.c | 22 +++++++++++++++++++---
>  5 files changed, 52 insertions(+), 7 deletions(-)
>
> diff --git a/NEWS b/NEWS
> index 94a347246..12905ac86 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -35,6 +35,9 @@ v3.3.0 - xx xxx xxxx
>         on mark and labels.
>       * 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.
> +       E.g. members of objects and elements of arrays are printed one per line,
> +       with indentation.
>     - Python:
>       * Added support for choosing the output format, e.g. 'json' or 'text'.
>     - ovs-vsctl:
> diff --git a/lib/unixctl.c b/lib/unixctl.c
> index 3b9f300ba..1b216795d 100644
> --- a/lib/unixctl.c
> +++ b/lib/unixctl.c
> @@ -553,7 +553,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[], int fmt_flags, char **result, char **err)

Perhaps it would be more consistent to use format_flags here as well, mirroring the structure definition. Additionally, using an unsigned int might be preferable, as outlined below.

>  {
>      struct jsonrpc_msg *request, *reply;
>      struct json **json_args, *params;
> @@ -590,7 +590,7 @@ unixctl_client_transact(struct jsonrpc *client, const char *command, int argc,
>              *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);
> +            *result = json_to_string(reply->result, fmt_flags);
>          } 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 35ef6a548..fe9160894 100644
> --- a/lib/unixctl.h
> +++ b/lib/unixctl.h
> @@ -39,6 +39,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[],
> +                            int fmt_flags,
>                              char **result, char **error);
>
>  /* Command registration. */
> diff --git a/tests/pmd.at b/tests/pmd.at
> index 57660c407..cff80da15 100644
> --- a/tests/pmd.at
> +++ b/tests/pmd.at
> @@ -112,8 +112,33 @@ dummy@ovs-dummy: hit:0 missed:0
>      p0 1/1: (dummy-pmd: n_rxq=1, n_txq=1, numa_id=0)
>  ])
>
> -AT_CHECK([ovs-appctl --format json dpif/show], [0], [dnl
> -[[{"ofprotos":[{"name":"br0","ports":[{"netdev_type":"dummy-internal","ofp_port":"65534","odp_port":"100","netdev_config":{},"netdev_name":"br0"},{"netdev_type":"dummy-pmd","ofp_port":"1","odp_port":"1","netdev_config":{"numa_id":"0","n_txq":"1","n_rxq":"1"},"netdev_name":"p0"}]}],"name":"dummy@ovs-dummy","stats":{"n_hit":"0","n_missed":"0"}}]]])

It might be worth considering retaining this test to ensure it functions properly even without the pretty options. Additionally, we could think about adding a separate test (not in pmd.at) to be executed with make check, specifically testing the JSON format with both pretty and non-pretty options in the final patch.

> +AT_CHECK([ovs-appctl --format json --pretty dpif/show], [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": {
> +              "n_rxq": "1",
> +              "n_txq": "1",
> +              "numa_id": "0"},
> +            "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
> diff --git a/utilities/ovs-appctl.c b/utilities/ovs-appctl.c
> index 02df8ba97..03c71ffad 100644
> --- a/utilities/ovs-appctl.c
> +++ b/utilities/ovs-appctl.c
> @@ -26,6 +26,7 @@
>  #include "daemon.h"
>  #include "dirs.h"
>  #include "openvswitch/dynamic-string.h"
> +#include "openvswitch/json.h"
>  #include "jsonrpc.h"
>  #include "process.h"
>  #include "timeval.h"
> @@ -39,6 +40,7 @@ static void usage(void);
>  /* Parsed command line args. */
>  struct cmdl_args {
>      enum ovs_output_fmt format;
> +    int format_flags;

Considering that this involves combining bit-masks, I suggest converting this to an unsigned int.

>      char *target;
>  };
>
> @@ -73,7 +75,7 @@ main(int argc, char *argv[])
>
>      if (opt_argv.n > 0) {
>          error = unixctl_client_transact(client, "set-options",
> -                                        opt_argv.n, opt_argv.names,
> +                                        opt_argv.n, opt_argv.names, 0,
>                                          &cmd_result, &cmd_error);
>
>          if (error) {
> @@ -97,7 +99,9 @@ main(int argc, char *argv[])
>      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_flags, &cmd_result,
> +                                    &cmd_error);
> +
>      if (error) {
>          ovs_fatal(error, "%s: transaction error", args->target);
>      }
> @@ -143,6 +147,11 @@ 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\
> +  --pretty           By default, JSON in output is printed as compactly as\n\
> +                     possible. This option causes JSON in output to be\n\
> +                     printed in a more readable fashion. Members of objects\n\
> +                     and elements of arrays are printed one per line, with\n\
> +                     indentation.\n\
>    -h, --help         Print this helpful information\n\
>    -V, --version      Display ovs-appctl version information\n",
>             program_name, program_name);
> @@ -154,6 +163,7 @@ cmdl_args_create(void) {
>      struct cmdl_args *args = xmalloc(sizeof *args);
>
>      args->format = OVS_OUTPUT_FMT_TEXT;
> +    args->format_flags = 0;
>      args->target = NULL;
>
>      return args;
> @@ -173,7 +183,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 +192,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,
> @@ -233,6 +245,10 @@ parse_command_line(int argc, char *argv[])
>              ovs_cmdl_print_options(long_options);
>              exit(EXIT_SUCCESS);
>
> +        case OPT_PRETTY:
> +            args->format_flags |= JSSF_PRETTY | JSSF_SORT;

Since these flags are only relevant for the JSON format, perhaps we should only set them if the requested format was JSON.
We could utilize a boolean variable here and do the verification/setting outside the loop. We might even consider raising an error if set with any mode other than JSON.

> +            break;
> +
>          case 'T':
>              if (!str_to_uint(optarg, 10, &timeout) || !timeout) {
>                  ovs_fatal(0, "value %s on -T or --timeout is invalid", optarg);
> -- 
> 2.39.2
Ilya Maximets March 19, 2024, 12:32 p.m. UTC | #2
On 3/15/24 11:27, Eelco Chaudron wrote:
> On 18 Jan 2024, at 16:26, jmeng@redhat.com wrote:
> 
>> From: Jakob Meng <code@jakobmeng.de>
>> diff --git a/lib/unixctl.h b/lib/unixctl.h
>> index 35ef6a548..fe9160894 100644
>> --- a/lib/unixctl.h
>> +++ b/lib/unixctl.h
>> @@ -39,6 +39,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[],
>> +                            int fmt_flags,
>>                              char **result, char **error);
>>
>>  /* Command registration. */
>> diff --git a/tests/pmd.at b/tests/pmd.at
>> index 57660c407..cff80da15 100644
>> --- a/tests/pmd.at
>> +++ b/tests/pmd.at
>> @@ -112,8 +112,33 @@ dummy@ovs-dummy: hit:0 missed:0
>>      p0 1/1: (dummy-pmd: n_rxq=1, n_txq=1, numa_id=0)
>>  ])
>>
>> -AT_CHECK([ovs-appctl --format json dpif/show], [0], [dnl
>> -[[{"ofprotos":[{"name":"br0","ports":[{"netdev_type":"dummy-internal","ofp_port":"65534","odp_port":"100","netdev_config":{},"netdev_name":"br0"},{"netdev_type":"dummy-pmd","ofp_port":"1","odp_port":"1","netdev_config":{"numa_id":"0","n_txq":"1","n_rxq":"1"},"netdev_name":"p0"}]}],"name":"dummy@ovs-dummy","stats":{"n_hit":"0","n_missed":"0"}}]]])
> 
> It might be worth considering retaining this test to ensure it functions properly even without the pretty options. Additionally, we could think about adding a separate test (not in pmd.at) to be executed with make check, specifically testing the JSON format with both pretty and non-pretty options in the final patch.

Keeping the test is a good thing, but I'd suggest to wrap the lines.
You could define the output once with m4_define and then compare
the non-pretty output with the result of m4_normalize and the pretty
one with the original.  Should be done in the previous patch and
expanded in this one.

BTW, is this output stable?  It doesn't seem to be sorted.  May cause
random test failures and different architectures or with different
compiler flags.

Best regards, Ilya Maximets.
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 94a347246..12905ac86 100644
--- a/NEWS
+++ b/NEWS
@@ -35,6 +35,9 @@  v3.3.0 - xx xxx xxxx
        on mark and labels.
      * 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.
+       E.g. members of objects and elements of arrays are printed one per line,
+       with indentation.
    - Python:
      * Added support for choosing the output format, e.g. 'json' or 'text'.
    - ovs-vsctl:
diff --git a/lib/unixctl.c b/lib/unixctl.c
index 3b9f300ba..1b216795d 100644
--- a/lib/unixctl.c
+++ b/lib/unixctl.c
@@ -553,7 +553,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[], int fmt_flags, char **result, char **err)
 {
     struct jsonrpc_msg *request, *reply;
     struct json **json_args, *params;
@@ -590,7 +590,7 @@  unixctl_client_transact(struct jsonrpc *client, const char *command, int argc,
             *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);
+            *result = json_to_string(reply->result, fmt_flags);
         } 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 35ef6a548..fe9160894 100644
--- a/lib/unixctl.h
+++ b/lib/unixctl.h
@@ -39,6 +39,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[],
+                            int fmt_flags,
                             char **result, char **error);
 
 /* Command registration. */
diff --git a/tests/pmd.at b/tests/pmd.at
index 57660c407..cff80da15 100644
--- a/tests/pmd.at
+++ b/tests/pmd.at
@@ -112,8 +112,33 @@  dummy@ovs-dummy: hit:0 missed:0
     p0 1/1: (dummy-pmd: n_rxq=1, n_txq=1, numa_id=0)
 ])
 
-AT_CHECK([ovs-appctl --format json dpif/show], [0], [dnl
-[[{"ofprotos":[{"name":"br0","ports":[{"netdev_type":"dummy-internal","ofp_port":"65534","odp_port":"100","netdev_config":{},"netdev_name":"br0"},{"netdev_type":"dummy-pmd","ofp_port":"1","odp_port":"1","netdev_config":{"numa_id":"0","n_txq":"1","n_rxq":"1"},"netdev_name":"p0"}]}],"name":"dummy@ovs-dummy","stats":{"n_hit":"0","n_missed":"0"}}]]])
+AT_CHECK([ovs-appctl --format json --pretty dpif/show], [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": {
+              "n_rxq": "1",
+              "n_txq": "1",
+              "numa_id": "0"},
+            "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
diff --git a/utilities/ovs-appctl.c b/utilities/ovs-appctl.c
index 02df8ba97..03c71ffad 100644
--- a/utilities/ovs-appctl.c
+++ b/utilities/ovs-appctl.c
@@ -26,6 +26,7 @@ 
 #include "daemon.h"
 #include "dirs.h"
 #include "openvswitch/dynamic-string.h"
+#include "openvswitch/json.h"
 #include "jsonrpc.h"
 #include "process.h"
 #include "timeval.h"
@@ -39,6 +40,7 @@  static void usage(void);
 /* Parsed command line args. */
 struct cmdl_args {
     enum ovs_output_fmt format;
+    int format_flags;
     char *target;
 };
 
@@ -73,7 +75,7 @@  main(int argc, char *argv[])
 
     if (opt_argv.n > 0) {
         error = unixctl_client_transact(client, "set-options",
-                                        opt_argv.n, opt_argv.names,
+                                        opt_argv.n, opt_argv.names, 0,
                                         &cmd_result, &cmd_error);
 
         if (error) {
@@ -97,7 +99,9 @@  main(int argc, char *argv[])
     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_flags, &cmd_result,
+                                    &cmd_error);
+
     if (error) {
         ovs_fatal(error, "%s: transaction error", args->target);
     }
@@ -143,6 +147,11 @@  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\
+  --pretty           By default, JSON in output is printed as compactly as\n\
+                     possible. This option causes JSON in output to be\n\
+                     printed in a more readable fashion. Members of objects\n\
+                     and elements of arrays are printed one per line, with\n\
+                     indentation.\n\
   -h, --help         Print this helpful information\n\
   -V, --version      Display ovs-appctl version information\n",
            program_name, program_name);
@@ -154,6 +163,7 @@  cmdl_args_create(void) {
     struct cmdl_args *args = xmalloc(sizeof *args);
 
     args->format = OVS_OUTPUT_FMT_TEXT;
+    args->format_flags = 0;
     args->target = NULL;
 
     return args;
@@ -173,7 +183,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 +192,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,
@@ -233,6 +245,10 @@  parse_command_line(int argc, char *argv[])
             ovs_cmdl_print_options(long_options);
             exit(EXIT_SUCCESS);
 
+        case OPT_PRETTY:
+            args->format_flags |= JSSF_PRETTY | JSSF_SORT;
+            break;
+
         case 'T':
             if (!str_to_uint(optarg, 10, &timeout) || !timeout) {
                 ovs_fatal(0, "value %s on -T or --timeout is invalid", optarg);