[ovs-dev] [PATCH v4 1/2] Add read-only option to ovs-dpctl and ovs-ofctl commands.

Ryan Moats rmoats at us.ibm.com
Mon Aug 15 03:53:26 UTC 2016


ovs-dpctl and ovs-ofctl lack a read-only option to prevent
running of commands that perform read-write operations.  Add
it and the necessary scaffolding to each.

Signed-off-by: Ryan Moats <rmoats at us.ibm.com>
---

v1->v2:
 Fixed typo in usage string

 lib/command-line.c             |  50 +++++++++----
 lib/command-line.h             |   6 +-
 lib/db-ctl-base.h              |   2 +-
 lib/dpctl.c                    |  44 ++++++------
 lib/dpctl.h                    |   3 +
 ovsdb/ovsdb-tool.c             |  28 ++++----
 tests/ovstest.c                |   4 +-
 tests/test-bitmap.c            |   6 +-
 tests/test-ccmap.c             |   6 +-
 tests/test-classifier.c        |  28 ++++----
 tests/test-cmap.c              |   6 +-
 tests/test-conntrack.c         |   6 +-
 tests/test-heap.c              |  14 ++--
 tests/test-jsonrpc.c           |  10 +--
 tests/test-netlink-conntrack.c |   8 +--
 tests/test-ovn.c               |  24 +++----
 tests/test-ovsdb.c             |  78 ++++++++++----------
 tests/test-reconnect.c         |  32 ++++-----
 tests/test-util.c              |  34 ++++-----
 tests/test-vconn.c             |  18 ++---
 utilities/ovs-dpctl.c          |   7 ++
 utilities/ovs-ofctl.8.in       |   3 +
 utilities/ovs-ofctl.c          | 160 ++++++++++++++++++++++-------------------
 23 files changed, 317 insertions(+), 260 deletions(-)

diff --git a/lib/command-line.c b/lib/command-line.c
index bda5ed6..7f30646 100644
--- a/lib/command-line.c
+++ b/lib/command-line.c
@@ -87,20 +87,11 @@ ovs_cmdl_print_options(const struct option options[])
     ds_destroy(&ds);
 }

-/* Runs the command designated by argv[0] within the command table specified by
- * 'commands', which must be terminated by a command whose 'name' member is a
- * null pointer.
- *
- * Command-line options should be stripped off, so that a typical invocation
- * looks like:
- *    struct ovs_cmdl_context ctx = {
- *        .argc = argc - optind,
- *        .argv = argv + optind,
- *    };
- *    ovs_cmdl_run_command(&ctx, my_commands);
- * */
-void
-ovs_cmdl_run_command(struct ovs_cmdl_context *ctx, const struct ovs_cmdl_command commands[])
+/* Whether to commit changes or not. */
+static bool dry_run;
+
+static void
+_ovs_cmdl_run_command(struct ovs_cmdl_context *ctx, const struct ovs_cmdl_command commands[])
 {
     const struct ovs_cmdl_command *p;

@@ -118,6 +109,9 @@ ovs_cmdl_run_command(struct ovs_cmdl_context *ctx, const struct ovs_cmdl_command
                 VLOG_FATAL("'%s' command takes at most %d arguments",
                            p->name, p->max_args);
             } else {
+                if (p->mode == OVS_RW && dry_run) {
+                    return;
+                }
                 p->handler(ctx);
                 if (ferror(stdout)) {
                     VLOG_FATAL("write to stdout failed");
@@ -132,6 +126,34 @@ ovs_cmdl_run_command(struct ovs_cmdl_context *ctx, const struct ovs_cmdl_command

     VLOG_FATAL("unknown command '%s'; use --help for help", ctx->argv[0]);
 }
+
+/* Runs the command designated by argv[0] within the command table specified by
+ * 'commands', which must be terminated by a command whose 'name' member is a
+ * null pointer.
+ *
+ * Command-line options should be stripped off, so that a typical invocation
+ * looks like:
+ *    struct ovs_cmdl_context ctx = {
+ *        .argc = argc - optind,
+ *        .argv = argv + optind,
+ *    };
+ *    ovs_cmdl_run_command(&ctx, my_commands);
+ * */
+void
+ovs_cmdl_run_command(struct ovs_cmdl_context *ctx,
+                     const struct ovs_cmdl_command commands[])
+{
+    dry_run = false;
+    _ovs_cmdl_run_command(ctx, commands);
+}
+
+void
+ovs_cmdl_run_command_dry_run(struct ovs_cmdl_context *ctx,
+                             const struct ovs_cmdl_command commands[])
+{
+    dry_run = true;
+    _ovs_cmdl_run_command(ctx, commands);
+}
 
 /* Process title. */

diff --git a/lib/command-line.h b/lib/command-line.h
index e9e3b7b..d0c4c3e 100644
--- a/lib/command-line.h
+++ b/lib/command-line.h
@@ -41,12 +41,16 @@ struct ovs_cmdl_command {
     int min_args;
     int max_args;
     ovs_cmdl_handler handler;
+    enum { OVS_RO, OVS_RW } mode;    /* Does this command modify things? */
 };

 char *ovs_cmdl_long_options_to_short_options(const struct option *options);
 void ovs_cmdl_print_options(const struct option *options);
 void ovs_cmdl_print_commands(const struct ovs_cmdl_command *commands);
-void ovs_cmdl_run_command(struct ovs_cmdl_context *, const struct ovs_cmdl_command[]);
+void ovs_cmdl_run_command(struct ovs_cmdl_context *,
+                          const struct ovs_cmdl_command[]);
+void ovs_cmdl_run_command_dry_run(struct ovs_cmdl_context *,
+                                  const struct ovs_cmdl_command[]);

 void ovs_cmdl_proctitle_init(int argc, char **argv);
 #if defined(__FreeBSD__) || defined(__NetBSD__)
diff --git a/lib/db-ctl-base.h b/lib/db-ctl-base.h
index 0f4658e..e5a354d 100644
--- a/lib/db-ctl-base.h
+++ b/lib/db-ctl-base.h
@@ -120,7 +120,7 @@ struct ctl_command_syntax {
      * empty string if the command does not support any options. */
     const char *options;

-    enum { RO, RW } mode;       /* Does this command modify the database? */
+    enum { RO, RW } mode;   /* Does this command modify the database? */
 };

 /* A command extracted from command-line input plus the structs for
diff --git a/lib/dpctl.c b/lib/dpctl.c
index b470ab0..1b7356e 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -59,6 +59,7 @@ struct dpctl_command {
     int min_args;
     int max_args;
     dpctl_command_handler *handler;
+    enum { DP_RO, DP_RW} mode;
 };
 static const struct dpctl_command *get_all_dpctl_commands(void);
 static void dpctl_print(struct dpctl_params *dpctl_p, const char *fmt, ...)
@@ -1615,29 +1616,29 @@ out:
 }
 
 static const struct dpctl_command all_commands[] = {
-    { "add-dp", "add-dp dp [iface...]", 1, INT_MAX, dpctl_add_dp },
-    { "del-dp", "del-dp dp", 1, 1, dpctl_del_dp },
-    { "add-if", "add-if dp iface...", 2, INT_MAX, dpctl_add_if },
-    { "del-if", "del-if dp iface...", 2, INT_MAX, dpctl_del_if },
-    { "set-if", "set-if dp iface...", 2, INT_MAX, dpctl_set_if },
-    { "dump-dps", "", 0, 0, dpctl_dump_dps },
-    { "show", "[dp...]", 0, INT_MAX, dpctl_show },
-    { "dump-flows", "[dp]", 0, 2, dpctl_dump_flows },
-    { "add-flow", "add-flow [dp] flow actions", 2, 3, dpctl_add_flow },
-    { "mod-flow", "mod-flow [dp] flow actions", 2, 3, dpctl_mod_flow },
-    { "get-flow", "get-flow [dp] ufid", 1, 2, dpctl_get_flow },
-    { "del-flow", "del-flow [dp] flow", 1, 2, dpctl_del_flow },
-    { "del-flows", "[dp]", 0, 1, dpctl_del_flows },
-    { "dump-conntrack", "[dp] [zone=N]", 0, 2, dpctl_dump_conntrack },
-    { "flush-conntrack", "[dp] [zone=N]", 0, 2, dpctl_flush_conntrack },
-    { "help", "", 0, INT_MAX, dpctl_help },
-    { "list-commands", "", 0, INT_MAX, dpctl_list_commands },
+    { "add-dp", "add-dp dp [iface...]", 1, INT_MAX, dpctl_add_dp, DP_RW },
+    { "del-dp", "del-dp dp", 1, 1, dpctl_del_dp, DP_RW },
+    { "add-if", "add-if dp iface...", 2, INT_MAX, dpctl_add_if, DP_RW },
+    { "del-if", "del-if dp iface...", 2, INT_MAX, dpctl_del_if, DP_RW },
+    { "set-if", "set-if dp iface...", 2, INT_MAX, dpctl_set_if, DP_RW },
+    { "dump-dps", "", 0, 0, dpctl_dump_dps, DP_RO },
+    { "show", "[dp...]", 0, INT_MAX, dpctl_show, DP_RO },
+    { "dump-flows", "[dp]", 0, 2, dpctl_dump_flows, DP_RO },
+    { "add-flow", "add-flow [dp] flow actions", 2, 3, dpctl_add_flow, DP_RW },
+    { "mod-flow", "mod-flow [dp] flow actions", 2, 3, dpctl_mod_flow, DP_RW },
+    { "get-flow", "get-flow [dp] ufid", 1, 2, dpctl_get_flow, DP_RO },
+    { "del-flow", "del-flow [dp] flow", 1, 2, dpctl_del_flow, DP_RW },
+    { "del-flows", "[dp]", 0, 1, dpctl_del_flows, DP_RW },
+    { "dump-conntrack", "[dp] [zone=N]", 0, 2, dpctl_dump_conntrack, DP_RO },
+    { "flush-conntrack", "[dp] [zone=N]", 0, 2, dpctl_flush_conntrack, DP_RW },
+    { "help", "", 0, INT_MAX, dpctl_help, DP_RO },
+    { "list-commands", "", 0, INT_MAX, dpctl_list_commands, DP_RO },

     /* Undocumented commands for testing. */
-    { "parse-actions", "actions", 1, INT_MAX, dpctl_parse_actions },
-    { "normalize-actions", "actions", 2, INT_MAX, dpctl_normalize_actions },
+    { "parse-actions", "actions", 1, INT_MAX, dpctl_parse_actions, DP_RO },
+    { "normalize-actions", "actions", 2, INT_MAX, dpctl_normalize_actions, DP_RO },

-    { NULL, NULL, 0, 0, NULL },
+    { NULL, NULL, 0, 0, NULL, DP_RO },
 };

 static const struct dpctl_command *get_all_dpctl_commands(void)
@@ -1672,6 +1673,9 @@ dpctl_run_command(int argc, const char *argv[], struct dpctl_params *dpctl_p)
                             p->name, p->max_args);
                 return EINVAL;
             } else {
+                if (p->mode == DP_RW && dpctl_p->dry_run) {
+                    return 0;
+                }
                 return p->handler(argc, argv, dpctl_p);
             }
         }
diff --git a/lib/dpctl.h b/lib/dpctl.h
index 11a172d..a4bdacf 100644
--- a/lib/dpctl.h
+++ b/lib/dpctl.h
@@ -33,6 +33,9 @@ struct dpctl_params {
     /* --may-create: Allow mod-flows command to create a new flow? */
     bool may_create;

+    /* --dry-run: Commit changes? */
+    bool dry_run;
+
     /* -m, --more: Increase output verbosity. */
     int verbosity;

diff --git a/ovsdb/ovsdb-tool.c b/ovsdb/ovsdb-tool.c
index af83da2..6531f6a 100644
--- a/ovsdb/ovsdb-tool.c
+++ b/ovsdb/ovsdb-tool.c
@@ -573,20 +573,20 @@ do_list_commands(struct ovs_cmdl_context *ctx OVS_UNUSED)
 }

 static const struct ovs_cmdl_command all_commands[] = {
-    { "create", "[db [schema]]", 0, 2, do_create },
-    { "compact", "[db [dst]]", 0, 2, do_compact },
-    { "convert", "[db [schema [dst]]]", 0, 3, do_convert },
-    { "needs-conversion", NULL, 0, 2, do_needs_conversion },
-    { "db-version", "[db]",  0, 1, do_db_version },
-    { "db-cksum", "[db]", 0, 1, do_db_cksum },
-    { "schema-version", "[schema]", 0, 1, do_schema_version },
-    { "schema-cksum", "[schema]", 0, 1, do_schema_cksum },
-    { "query", "[db] trns", 1, 2, do_query },
-    { "transact", "[db] trns", 1, 2, do_transact },
-    { "show-log", "[db]", 0, 1, do_show_log },
-    { "help", NULL, 0, INT_MAX, do_help },
-    { "list-commands", NULL, 0, INT_MAX, do_list_commands },
-    { NULL, NULL, 0, 0, NULL },
+    { "create", "[db [schema]]", 0, 2, do_create, OVS_RW },
+    { "compact", "[db [dst]]", 0, 2, do_compact, OVS_RW },
+    { "convert", "[db [schema [dst]]]", 0, 3, do_convert, OVS_RW },
+    { "needs-conversion", NULL, 0, 2, do_needs_conversion, OVS_RO },
+    { "db-version", "[db]",  0, 1, do_db_version, OVS_RO },
+    { "db-cksum", "[db]", 0, 1, do_db_cksum, OVS_RO },
+    { "schema-version", "[schema]", 0, 1, do_schema_version, OVS_RO },
+    { "schema-cksum", "[schema]", 0, 1, do_schema_cksum, OVS_RO },
+    { "query", "[db] trns", 1, 2, do_query, OVS_RO },
+    { "transact", "[db] trns", 1, 2, do_transact, OVS_RO },
+    { "show-log", "[db]", 0, 1, do_show_log, OVS_RO },
+    { "help", NULL, 0, INT_MAX, do_help, OVS_RO },
+    { "list-commands", NULL, 0, INT_MAX, do_list_commands, OVS_RO },
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };

 static const struct ovs_cmdl_command *get_all_commands(void)
diff --git a/tests/ovstest.c b/tests/ovstest.c
index 2fb7f66..745bd2e 100644
--- a/tests/ovstest.c
+++ b/tests/ovstest.c
@@ -33,7 +33,7 @@ static size_t allocated_commands = 0;
 static void
 add_command(struct ovs_cmdl_command *cmd)
 {
-    const struct ovs_cmdl_command nil = {NULL, NULL, 0, 0, NULL};
+    const struct ovs_cmdl_command nil = {NULL, NULL, 0, 0, NULL, OVS_RO};

     while (n_commands + 1 >= allocated_commands) {
         commands = x2nrealloc(commands, &allocated_commands,
@@ -86,7 +86,7 @@ help(struct ovs_cmdl_context *ctx OVS_UNUSED)
 static void
 add_top_level_commands(void)
 {
-    struct ovs_cmdl_command help_cmd = {"--help", NULL, 0, 0, help};
+    struct ovs_cmdl_command help_cmd = {"--help", NULL, 0, 0, help, OVS_RO };

     add_command(&help_cmd);
 }
diff --git a/tests/test-bitmap.c b/tests/test-bitmap.c
index 3dbc8df..484407b 100644
--- a/tests/test-bitmap.c
+++ b/tests/test-bitmap.c
@@ -149,9 +149,9 @@ run_benchmarks(struct ovs_cmdl_context *ctx)
 }

 static const struct ovs_cmdl_command commands[] = {
-    {"check", NULL, 0, 0, run_tests},
-    {"benchmark", NULL, 1, 1, run_benchmarks},
-    {NULL, NULL, 0, 0, NULL},
+    {"check", NULL, 0, 0, run_tests, OVS_RO},
+    {"benchmark", NULL, 1, 1, run_benchmarks, OVS_RO},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };

 static void
diff --git a/tests/test-ccmap.c b/tests/test-ccmap.c
index 4efe1b9..5ebc659 100644
--- a/tests/test-ccmap.c
+++ b/tests/test-ccmap.c
@@ -272,9 +272,9 @@ benchmark_ccmap(void)

 
 static const struct ovs_cmdl_command commands[] = {
-    {"check", NULL, 0, 1, run_tests},
-    {"benchmark", NULL, 3, 3, run_benchmarks},
-    {NULL, NULL, 0, 0, NULL},
+    {"check", NULL, 0, 1, run_tests, OVS_RO},
+    {"benchmark", NULL, 3, 3, run_benchmarks, OVS_RO},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };

 static void
diff --git a/tests/test-classifier.c b/tests/test-classifier.c
index cdd83f0..ebbc283 100644
--- a/tests/test-classifier.c
+++ b/tests/test-classifier.c
@@ -1838,23 +1838,23 @@ static void help(struct ovs_cmdl_context *ctx);

 static const struct ovs_cmdl_command commands[] = {
     /* Classifier tests. */
-    {"empty", NULL, 0, 0, test_empty},
-    {"destroy-null", NULL, 0, 0, test_destroy_null},
-    {"single-rule", NULL, 0, 0, test_single_rule},
-    {"rule-replacement", NULL, 0, 0, test_rule_replacement},
-    {"many-rules-in-one-list", NULL, 0, 1, test_many_rules_in_one_list},
-    {"many-rules-in-one-table", NULL, 0, 1, test_many_rules_in_one_table},
-    {"many-rules-in-two-tables", NULL, 0, 0, test_many_rules_in_two_tables},
-    {"many-rules-in-five-tables", NULL, 0, 0, test_many_rules_in_five_tables},
-    {"benchmark", NULL, 0, 5, run_benchmarks},
+    {"empty", NULL, 0, 0, test_empty, OVS_RO },
+    {"destroy-null", NULL, 0, 0, test_destroy_null, OVS_RO },
+    {"single-rule", NULL, 0, 0, test_single_rule, OVS_RO },
+    {"rule-replacement", NULL, 0, 0, test_rule_replacement, OVS_RO },
+    {"many-rules-in-one-list", NULL, 0, 1, test_many_rules_in_one_list, OVS_RO },
+    {"many-rules-in-one-table", NULL, 0, 1, test_many_rules_in_one_table, OVS_RO },
+    {"many-rules-in-two-tables", NULL, 0, 0, test_many_rules_in_two_tables, OVS_RO },
+    {"many-rules-in-five-tables", NULL, 0, 0, test_many_rules_in_five_tables, OVS_RO },
+    {"benchmark", NULL, 0, 5, run_benchmarks, OVS_RO },

     /* Miniflow and minimask tests. */
-    {"miniflow", NULL, 0, 0, test_miniflow},
-    {"minimask_has_extra", NULL, 0, 0, test_minimask_has_extra},
-    {"minimask_combine", NULL, 0, 0, test_minimask_combine},
+    {"miniflow", NULL, 0, 0, test_miniflow, OVS_RO },
+    {"minimask_has_extra", NULL, 0, 0, test_minimask_has_extra, OVS_RO },
+    {"minimask_combine", NULL, 0, 0, test_minimask_combine, OVS_RO },

-    {"--help", NULL, 0, 0, help},
-    {NULL, NULL, 0, 0, NULL},
+    {"--help", NULL, 0, 0, help, OVS_RO },
+    {NULL, NULL, 0, 0, NULL, OVS_RO },
 };

 static void
diff --git a/tests/test-cmap.c b/tests/test-cmap.c
index 4cac7de..e159a16 100644
--- a/tests/test-cmap.c
+++ b/tests/test-cmap.c
@@ -636,9 +636,9 @@ benchmark_hmap(void)
 }
 
 static const struct ovs_cmdl_command commands[] = {
-    {"check", NULL, 0, 1, run_tests},
-    {"benchmark", NULL, 3, 4, run_benchmarks},
-    {NULL, NULL, 0, 0, NULL},
+    {"check", NULL, 0, 1, run_tests, OVS_RO},
+    {"benchmark", NULL, 3, 4, run_benchmarks, OVS_RO},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };

 static void
diff --git a/tests/test-conntrack.c b/tests/test-conntrack.c
index 6c1b373..803e2b9 100644
--- a/tests/test-conntrack.c
+++ b/tests/test-conntrack.c
@@ -260,13 +260,13 @@ static const struct ovs_cmdl_command commands[] = {
      * is '1', each packet in a batch will have a different source and
      * destination port */
     {"benchmark", "n_threads n_pkts batch_size [change_connection]", 3, 4,
-     test_benchmark},
+     test_benchmark, OVS_RO},
     /* Reads packets from 'file' and sends them to the connection tracker,
      * 'batch_size' (1 by default) per call, with the commit flag set.
      * Prints the ct_state of each packet. */
-    {"pcap", "file [batch_size]", 1, 2, test_pcap},
+    {"pcap", "file [batch_size]", 1, 2, test_pcap, OVS_RO},

-    {NULL, NULL, 0, 0, NULL},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };

 static void
diff --git a/tests/test-heap.c b/tests/test-heap.c
index 6dab22b..88c9f25 100644
--- a/tests/test-heap.c
+++ b/tests/test-heap.c
@@ -461,16 +461,16 @@ test_heap_raw_delete(struct ovs_cmdl_context *ctx OVS_UNUSED)

 static const struct ovs_cmdl_command commands[] = {
     { "insert-delete-same-order", NULL, 0, 0,
-      test_heap_insert_delete_same_order, },
+      test_heap_insert_delete_same_order, OVS_RO },
     { "insert-delete-reverse-order", NULL, 0, 0,
-      test_heap_insert_delete_reverse_order, },
+      test_heap_insert_delete_reverse_order, OVS_RO },
     { "insert-delete-every-order", NULL, 0, 0,
-      test_heap_insert_delete_every_order, },
+      test_heap_insert_delete_every_order, OVS_RO },
     { "insert-delete-same-order-with-dups", NULL, 0, 0,
-      test_heap_insert_delete_same_order_with_dups, },
-    { "raw-insert", NULL, 0, 0, test_heap_raw_insert, },
-    { "raw-delete", NULL, 0, 0, test_heap_raw_delete, },
-    { NULL, NULL, 0, 0, NULL, },
+      test_heap_insert_delete_same_order_with_dups, OVS_RO },
+    { "raw-insert", NULL, 0, 0, test_heap_raw_insert, OVS_RO },
+    { "raw-delete", NULL, 0, 0, test_heap_raw_delete, OVS_RO },
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };

 static void
diff --git a/tests/test-jsonrpc.c b/tests/test-jsonrpc.c
index be79064..684601a 100644
--- a/tests/test-jsonrpc.c
+++ b/tests/test-jsonrpc.c
@@ -331,11 +331,11 @@ do_help(struct ovs_cmdl_context *ctx OVS_UNUSED)
 }

 static struct ovs_cmdl_command all_commands[] = {
-    { "listen", NULL, 1, 1, do_listen },
-    { "request", NULL, 3, 3, do_request },
-    { "notify", NULL, 3, 3, do_notify },
-    { "help", NULL, 0, INT_MAX, do_help },
-    { NULL, NULL, 0, 0, NULL },
+    { "listen", NULL, 1, 1, do_listen, OVS_RO },
+    { "request", NULL, 3, 3, do_request, OVS_RO },
+    { "notify", NULL, 3, 3, do_notify, OVS_RO },
+    { "help", NULL, 0, INT_MAX, do_help, OVS_RO },
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };

 static struct ovs_cmdl_command *
diff --git a/tests/test-netlink-conntrack.c b/tests/test-netlink-conntrack.c
index f0d48f7..000062d 100644
--- a/tests/test-netlink-conntrack.c
+++ b/tests/test-netlink-conntrack.c
@@ -161,14 +161,14 @@ static const struct ovs_cmdl_command commands[] = {
     /* Linux netlink connection tracker interface test. */

     /* Prints all the entries in the connection table and exits. */
-    {"dump", "[zone=zone]", 0, 1, test_nl_ct_dump},
+    {"dump", "[zone=zone]", 0, 1, test_nl_ct_dump, OVS_RO},
     /* Listens to all the connection tracking events and prints them to
      * standard output until killed. */
-    {"monitor", "", 0, 0, test_nl_ct_monitor},
+    {"monitor", "", 0, 0, test_nl_ct_monitor, OVS_RO},
     /* Flushes all the entries from all the tables.. */
-    {"flush", "[zone=zone]", 0, 1, test_nl_ct_flush},
+    {"flush", "[zone=zone]", 0, 1, test_nl_ct_flush, OVS_RO},

-    {NULL, NULL, 0, 0, NULL},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };

 static void
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index acb6a99..32e5f57 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -1532,23 +1532,23 @@ test_ovn_main(int argc, char *argv[])

     static const struct ovs_cmdl_command commands[] = {
         /* Lexer. */
-        {"lex", NULL, 0, 0, test_lex},
+        {"lex", NULL, 0, 0, test_lex, OVS_RO},

         /* Expressions. */
-        {"parse-expr", NULL, 0, 0, test_parse_expr},
-        {"annotate-expr", NULL, 0, 0, test_annotate_expr},
-        {"simplify-expr", NULL, 0, 0, test_simplify_expr},
-        {"normalize-expr", NULL, 0, 0, test_normalize_expr},
-        {"expr-to-flows", NULL, 0, 0, test_expr_to_flows},
-        {"evaluate-expr", NULL, 1, 1, test_evaluate_expr},
-        {"composition", NULL, 1, 1, test_composition},
-        {"tree-shape", NULL, 1, 1, test_tree_shape},
-        {"exhaustive", NULL, 1, 1, test_exhaustive},
+        {"parse-expr", NULL, 0, 0, test_parse_expr, OVS_RO},
+        {"annotate-expr", NULL, 0, 0, test_annotate_expr, OVS_RO},
+        {"simplify-expr", NULL, 0, 0, test_simplify_expr, OVS_RO},
+        {"normalize-expr", NULL, 0, 0, test_normalize_expr, OVS_RO},
+        {"expr-to-flows", NULL, 0, 0, test_expr_to_flows, OVS_RO},
+        {"evaluate-expr", NULL, 1, 1, test_evaluate_expr, OVS_RO},
+        {"composition", NULL, 1, 1, test_composition, OVS_RO},
+        {"tree-shape", NULL, 1, 1, test_tree_shape, OVS_RO},
+        {"exhaustive", NULL, 1, 1, test_exhaustive, OVS_RO},

         /* Actions. */
-        {"parse-actions", NULL, 0, 0, test_parse_actions},
+        {"parse-actions", NULL, 0, 0, test_parse_actions, OVS_RO},

-        {NULL, NULL, 0, 0, NULL},
+        {NULL, NULL, 0, 0, NULL, OVS_RO},
     };
     struct ovs_cmdl_context ctx;
     ctx.argc = argc - optind;
diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c
index 4a68bca..5d89412 100644
--- a/tests/test-ovsdb.c
+++ b/tests/test-ovsdb.c
@@ -1667,13 +1667,13 @@ static void
 do_transact(struct ovs_cmdl_context *ctx)
 {
     static const struct ovs_cmdl_command do_transact_commands[] = {
-        { "commit", NULL, 0, 0, do_transact_commit },
-        { "abort", NULL, 0, 0, do_transact_abort },
-        { "insert", NULL, 2, 3, do_transact_insert },
-        { "delete", NULL, 1, 1, do_transact_delete },
-        { "modify", NULL, 2, 3, do_transact_modify },
-        { "print", NULL, 0, 0, do_transact_print },
-        { NULL, NULL, 0, 0, NULL },
+        { "commit", NULL, 0, 0, do_transact_commit, OVS_RO },
+        { "abort", NULL, 0, 0, do_transact_abort, OVS_RO },
+        { "insert", NULL, 2, 3, do_transact_insert, OVS_RO },
+        { "delete", NULL, 1, 1, do_transact_delete, OVS_RO },
+        { "modify", NULL, 2, 3, do_transact_modify, OVS_RO },
+        { "print", NULL, 0, 0, do_transact_print, OVS_RO },
+        { NULL, NULL, 0, 0, NULL, OVS_RO },
     };

     struct ovsdb_schema *schema;
@@ -2578,39 +2578,39 @@ do_idl_partial_update_map_column(struct ovs_cmdl_context *ctx)
 }

 static struct ovs_cmdl_command all_commands[] = {
-    { "log-io", NULL, 2, INT_MAX, do_log_io },
-    { "default-atoms", NULL, 0, 0, do_default_atoms },
-    { "default-data", NULL, 0, 0, do_default_data },
-    { "diff-data", NULL, 3, INT_MAX, do_diff_data},
-    { "parse-atomic-type", NULL, 1, 1, do_parse_atomic_type },
-    { "parse-base-type", NULL, 1, 1, do_parse_base_type },
-    { "parse-type", NULL, 1, 1, do_parse_type },
-    { "parse-atoms", NULL, 2, INT_MAX, do_parse_atoms },
-    { "parse-atom-strings", NULL, 2, INT_MAX, do_parse_atom_strings },
-    { "parse-data", NULL, 2, INT_MAX, do_parse_data },
-    { "parse-data-strings", NULL, 2, INT_MAX, do_parse_data_strings },
-    { "sort-atoms", NULL, 2, 2, do_sort_atoms },
-    { "parse-column", NULL, 2, 2, do_parse_column },
-    { "parse-table", NULL, 2, 3, do_parse_table },
-    { "parse-rows", NULL, 2, INT_MAX, do_parse_rows },
-    { "compare-rows", NULL, 2, INT_MAX, do_compare_rows },
-    { "parse-conditions", NULL, 2, INT_MAX, do_parse_conditions },
-    { "evaluate-conditions", NULL, 3, 3, do_evaluate_conditions },
-    { "evaluate-conditions-any", NULL, 3, 3, do_evaluate_conditions_any },
-    { "compare-conditions", NULL, 2, 2, do_compare_conditions },
-    { "parse-mutations", NULL, 2, INT_MAX, do_parse_mutations },
-    { "execute-mutations", NULL, 3, 3, do_execute_mutations },
-    { "query", NULL, 3, 3, do_query },
-    { "query-distinct", NULL, 4, 4, do_query_distinct },
-    { "transact", NULL, 1, INT_MAX, do_transact },
-    { "parse-schema", NULL, 1, 1, do_parse_schema },
-    { "execute", NULL, 2, INT_MAX, do_execute },
-    { "trigger", NULL, 2, INT_MAX, do_trigger },
-    { "idl", NULL, 1, INT_MAX, do_idl },
+    { "log-io", NULL, 2, INT_MAX, do_log_io, OVS_RO },
+    { "default-atoms", NULL, 0, 0, do_default_atoms, OVS_RO },
+    { "default-data", NULL, 0, 0, do_default_data, OVS_RO },
+    { "diff-data", NULL, 3, INT_MAX, do_diff_data, OVS_RO },
+    { "parse-atomic-type", NULL, 1, 1, do_parse_atomic_type, OVS_RO },
+    { "parse-base-type", NULL, 1, 1, do_parse_base_type, OVS_RO },
+    { "parse-type", NULL, 1, 1, do_parse_type, OVS_RO },
+    { "parse-atoms", NULL, 2, INT_MAX, do_parse_atoms, OVS_RO },
+    { "parse-atom-strings", NULL, 2, INT_MAX, do_parse_atom_strings, OVS_RO },
+    { "parse-data", NULL, 2, INT_MAX, do_parse_data, OVS_RO },
+    { "parse-data-strings", NULL, 2, INT_MAX, do_parse_data_strings, OVS_RO },
+    { "sort-atoms", NULL, 2, 2, do_sort_atoms, OVS_RO },
+    { "parse-column", NULL, 2, 2, do_parse_column, OVS_RO },
+    { "parse-table", NULL, 2, 3, do_parse_table, OVS_RO },
+    { "parse-rows", NULL, 2, INT_MAX, do_parse_rows, OVS_RO },
+    { "compare-rows", NULL, 2, INT_MAX, do_compare_rows, OVS_RO },
+    { "parse-conditions", NULL, 2, INT_MAX, do_parse_conditions, OVS_RO },
+    { "evaluate-conditions", NULL, 3, 3, do_evaluate_conditions, OVS_RO },
+    { "evaluate-conditions-any", NULL, 3, 3, do_evaluate_conditions_any, OVS_RO },
+    { "compare-conditions", NULL, 2, 2, do_compare_conditions, OVS_RO },
+    { "parse-mutations", NULL, 2, INT_MAX, do_parse_mutations, OVS_RO },
+    { "execute-mutations", NULL, 3, 3, do_execute_mutations, OVS_RO },
+    { "query", NULL, 3, 3, do_query, OVS_RO },
+    { "query-distinct", NULL, 4, 4, do_query_distinct, OVS_RO },
+    { "transact", NULL, 1, INT_MAX, do_transact, OVS_RO },
+    { "parse-schema", NULL, 1, 1, do_parse_schema, OVS_RO },
+    { "execute", NULL, 2, INT_MAX, do_execute, OVS_RO },
+    { "trigger", NULL, 2, INT_MAX, do_trigger, OVS_RO },
+    { "idl", NULL, 1, INT_MAX, do_idl, OVS_RO },
     { "idl-partial-update-map-column", NULL, 1, INT_MAX,
-                                       do_idl_partial_update_map_column },
-    { "help", NULL, 0, INT_MAX, do_help },
-    { NULL, NULL, 0, 0, NULL },
+                                       do_idl_partial_update_map_column, OVS_RO },
+    { "help", NULL, 0, INT_MAX, do_help, OVS_RO },
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };

 static struct ovs_cmdl_command *
diff --git a/tests/test-reconnect.c b/tests/test-reconnect.c
index 76e43ea..72252b8 100644
--- a/tests/test-reconnect.c
+++ b/tests/test-reconnect.c
@@ -272,22 +272,22 @@ do_listen_error(struct ovs_cmdl_context *ctx)
 }

 static const struct ovs_cmdl_command all_commands[] = {
-    { "enable", NULL, 0, 0, do_enable },
-    { "disable", NULL, 0, 0, do_disable },
-    { "force-reconnect", NULL, 0, 0, do_force_reconnect },
-    { "disconnected", NULL, 0, 1, do_disconnected },
-    { "connecting", NULL, 0, 0, do_connecting },
-    { "connect-failed", NULL, 0, 1, do_connect_failed },
-    { "connected", NULL, 0, 0, do_connected },
-    { "activity", NULL, 0, 0, do_activity },
-    { "run", NULL, 0, 1, do_run },
-    { "advance", NULL, 1, 1, do_advance },
-    { "timeout", NULL, 0, 0, do_timeout },
-    { "set-max-tries", NULL, 1, 1, do_set_max_tries },
-    { "passive", NULL, 0, 0, do_set_passive },
-    { "listening", NULL, 0, 0, do_listening },
-    { "listen-error", NULL, 1, 1, do_listen_error },
-    { NULL, NULL, 0, 0, NULL },
+    { "enable", NULL, 0, 0, do_enable, OVS_RO },
+    { "disable", NULL, 0, 0, do_disable, OVS_RO },
+    { "force-reconnect", NULL, 0, 0, do_force_reconnect, OVS_RO },
+    { "disconnected", NULL, 0, 1, do_disconnected, OVS_RO },
+    { "connecting", NULL, 0, 0, do_connecting, OVS_RO },
+    { "connect-failed", NULL, 0, 1, do_connect_failed, OVS_RO },
+    { "connected", NULL, 0, 0, do_connected, OVS_RO },
+    { "activity", NULL, 0, 0, do_activity, OVS_RO },
+    { "run", NULL, 0, 1, do_run, OVS_RO },
+    { "advance", NULL, 1, 1, do_advance, OVS_RO },
+    { "timeout", NULL, 0, 0, do_timeout, OVS_RO },
+    { "set-max-tries", NULL, 1, 1, do_set_max_tries, OVS_RO },
+    { "passive", NULL, 0, 0, do_set_passive, OVS_RO },
+    { "listening", NULL, 0, 0, do_listening, OVS_RO },
+    { "listen-error", NULL, 1, 1, do_listen_error, OVS_RO },
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };

 static const struct ovs_cmdl_command *
diff --git a/tests/test-util.c b/tests/test-util.c
index ef45903..e37c722 100644
--- a/tests/test-util.c
+++ b/tests/test-util.c
@@ -1149,25 +1149,25 @@ test_file_name(struct ovs_cmdl_context *ctx)
 #endif /* _WIN32 */
 
 static const struct ovs_cmdl_command commands[] = {
-    {"ctz", NULL, 0, 0, test_ctz},
-    {"clz", NULL, 0, 0, test_clz},
-    {"round_up_pow2", NULL, 0, 0, test_round_up_pow2},
-    {"round_down_pow2", NULL, 0, 0, test_round_down_pow2},
-    {"count_1bits", NULL, 0, 0, test_count_1bits},
-    {"log_2_floor", NULL, 0, 0, test_log_2_floor},
-    {"bitwise_copy", NULL, 0, 0, test_bitwise_copy},
-    {"bitwise_zero", NULL, 0, 0, test_bitwise_zero},
-    {"bitwise_one", NULL, 0, 0, test_bitwise_one},
-    {"bitwise_is_all_zeros", NULL, 0, 0, test_bitwise_is_all_zeros},
-    {"bitwise_rscan", NULL, 0, 0, test_bitwise_rscan},
-    {"follow-symlinks", NULL, 1, INT_MAX, test_follow_symlinks},
-    {"assert", NULL, 0, 0, test_assert},
-    {"ovs_scan", NULL, 0, 0, test_ovs_scan},
-    {"snprintf", NULL, 0, 0, test_snprintf},
+    {"ctz", NULL, 0, 0, test_ctz, OVS_RO},
+    {"clz", NULL, 0, 0, test_clz, OVS_RO},
+    {"round_up_pow2", NULL, 0, 0, test_round_up_pow2, OVS_RO},
+    {"round_down_pow2", NULL, 0, 0, test_round_down_pow2, OVS_RO},
+    {"count_1bits", NULL, 0, 0, test_count_1bits, OVS_RO},
+    {"log_2_floor", NULL, 0, 0, test_log_2_floor, OVS_RO},
+    {"bitwise_copy", NULL, 0, 0, test_bitwise_copy, OVS_RO},
+    {"bitwise_zero", NULL, 0, 0, test_bitwise_zero, OVS_RO},
+    {"bitwise_one", NULL, 0, 0, test_bitwise_one, OVS_RO},
+    {"bitwise_is_all_zeros", NULL, 0, 0, test_bitwise_is_all_zeros, OVS_RO},
+    {"bitwise_rscan", NULL, 0, 0, test_bitwise_rscan, OVS_RO},
+    {"follow-symlinks", NULL, 1, INT_MAX, test_follow_symlinks, OVS_RO},
+    {"assert", NULL, 0, 0, test_assert, OVS_RO},
+    {"ovs_scan", NULL, 0, 0, test_ovs_scan, OVS_RO},
+    {"snprintf", NULL, 0, 0, test_snprintf, OVS_RO},
 #ifndef _WIN32
-    {"file_name", NULL, 1, INT_MAX, test_file_name},
+    {"file_name", NULL, 1, INT_MAX, test_file_name, OVS_RO},
 #endif
-    {NULL, NULL, 0, 0, NULL},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };

 static void
diff --git a/tests/test-vconn.c b/tests/test-vconn.c
index faa824a..1bf9aa0 100644
--- a/tests/test-vconn.c
+++ b/tests/test-vconn.c
@@ -432,15 +432,15 @@ test_send_invalid_version_hello(struct ovs_cmdl_context *ctx)
 }

 static const struct ovs_cmdl_command commands[] = {
-    {"refuse-connection", NULL, 1, 1, test_refuse_connection},
-    {"accept-then-close", NULL, 1, 1, test_accept_then_close},
-    {"read-hello", NULL, 1, 1, test_read_hello},
-    {"send-plain-hello", NULL, 1, 1, test_send_plain_hello},
-    {"send-long-hello", NULL, 1, 1, test_send_long_hello},
-    {"send-echo-hello", NULL, 1, 1, test_send_echo_hello},
-    {"send-short-hello", NULL, 1, 1, test_send_short_hello},
-    {"send-invalid-version-hello", NULL, 1, 1, test_send_invalid_version_hello},
-    {NULL, NULL, 0, 0, NULL},
+    {"refuse-connection", NULL, 1, 1, test_refuse_connection, OVS_RO},
+    {"accept-then-close", NULL, 1, 1, test_accept_then_close, OVS_RO},
+    {"read-hello", NULL, 1, 1, test_read_hello, OVS_RO},
+    {"send-plain-hello", NULL, 1, 1, test_send_plain_hello, OVS_RO},
+    {"send-long-hello", NULL, 1, 1, test_send_long_hello, OVS_RO},
+    {"send-echo-hello", NULL, 1, 1, test_send_echo_hello, OVS_RO},
+    {"send-short-hello", NULL, 1, 1, test_send_short_hello, OVS_RO},
+    {"send-invalid-version-hello", NULL, 1, 1, test_send_invalid_version_hello, OVS_RO},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };

 static void
diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
index 4897ea3..c52f946 100644
--- a/utilities/ovs-dpctl.c
+++ b/utilities/ovs-dpctl.c
@@ -77,12 +77,14 @@ parse_options(int argc, char *argv[])
     enum {
         OPT_CLEAR = UCHAR_MAX + 1,
         OPT_MAY_CREATE,
+        OPT_DRY_RUN,
         VLOG_OPTION_ENUMS
     };
     static const struct option long_options[] = {
         {"statistics", no_argument, NULL, 's'},
         {"clear", no_argument, NULL, OPT_CLEAR},
         {"may-create", no_argument, NULL, OPT_MAY_CREATE},
+        {"dry-run", no_argument, NULL, OPT_DRY_RUN},
         {"more", no_argument, NULL, 'm'},
         {"timeout", required_argument, NULL, 't'},
         {"help", no_argument, NULL, 'h'},
@@ -115,6 +117,10 @@ parse_options(int argc, char *argv[])
             dpctl_p.may_create = true;
             break;

+        case OPT_DRY_RUN:
+            dpctl_p.dry_run = true;
+            break;
+
         case 'm':
             dpctl_p.verbosity++;
             break;
@@ -186,6 +192,7 @@ usage(void *userdata OVS_UNUSED)
            "  -m, --more                  increase verbosity of output\n"
            "\nOptions for mod-flow:\n"
            "  --may-create                create flow if it doesn't exist\n"
+           "  --dry-run                   do not commit changes\n"
            "  --clear                     reset existing stats to zero\n"
            "\nOther options:\n"
            "  -t, --timeout=SECS          give up after SECS seconds\n"
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index 84057c0..a8d84df 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -2968,6 +2968,9 @@ depending on its configuration.
 \fB\-\-strict\fR
 Uses strict matching when running flow modification commands.
 .
+.IP "\fB\-\-dry-run\fR"
+Do not commit any changes.
+.
 .IP "\fB\-\-bundle\fR"
 Execute flow mods as an OpenFlow 1.4 atomic bundle transaction.
 .RS
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index 42d358f..361a1a2 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -79,6 +79,9 @@ static bool bundle = false;
 /* --color: Use color markers. */
 static bool enable_color;

+/* --dry-run: Do not commit any changes. */
+static bool dry_run;
+
 /* --strict: Use strict matching for flow mod commands?  Additionally governs
  * use of nx_pull_match() instead of nx_pull_match_loose() in parse-nx-match.
  */
@@ -142,7 +145,11 @@ main(int argc, char *argv[])
     ctx.argv = argv + optind;

     daemon_become_new_user(false);
-    ovs_cmdl_run_command(&ctx, get_all_commands());
+    if (dry_run) {
+        ovs_cmdl_run_command_dry_run(&ctx, get_all_commands());
+    } else {
+        ovs_cmdl_run_command(&ctx, get_all_commands());
+    }
     return 0;
 }

@@ -180,6 +187,7 @@ parse_options(int argc, char *argv[])
         OPT_BUNDLE,
         OPT_COLOR,
         OPT_MAY_CREATE,
+        OPT_DRY_RUN,
         DAEMON_OPTION_ENUMS,
         OFP_VERSION_OPTION_ENUMS,
         VLOG_OPTION_ENUMS
@@ -200,6 +208,7 @@ parse_options(int argc, char *argv[])
         {"bundle", no_argument, NULL, OPT_BUNDLE},
         {"color", optional_argument, NULL, OPT_COLOR},
         {"may-create", no_argument, NULL, OPT_MAY_CREATE},
+        {"dry-run", no_argument, NULL, OPT_DRY_RUN},
         DAEMON_LONG_OPTIONS,
         OFP_VERSION_LONG_OPTIONS,
         VLOG_LONG_OPTIONS,
@@ -281,6 +290,10 @@ parse_options(int argc, char *argv[])
             strict = true;
             break;

+        case OPT_DRY_RUN:
+            dry_run = true;
+            break;
+
         case OPT_READD:
             readd = true;
             break;
@@ -452,6 +465,7 @@ usage(void)
     vlog_usage();
     printf("\nOther options:\n"
            "  --strict                    use strict match for flow commands\n"
+           "  --dry-run                   do not commit changes\n"
            "  --readd                     replace flows that haven't changed\n"
            "  -F, --flow-format=FORMAT    force particular flow format\n"
            "  -P, --packet-in-format=FRMT force particular packet in format\n"
@@ -4120,136 +4134,136 @@ ofctl_parse_key_value(struct ovs_cmdl_context *ctx)

 static const struct ovs_cmdl_command all_commands[] = {
     { "show", "switch",
-      1, 1, ofctl_show },
+      1, 1, ofctl_show, OVS_RO },
     { "monitor", "switch [misslen] [invalid_ttl] [watch:[...]]",
-      1, 3, ofctl_monitor },
+      1, 3, ofctl_monitor, OVS_RO },
     { "snoop", "switch",
-      1, 1, ofctl_snoop },
+      1, 1, ofctl_snoop, OVS_RO },
     { "dump-desc", "switch",
-      1, 1, ofctl_dump_desc },
+      1, 1, ofctl_dump_desc, OVS_RO },
     { "dump-tables", "switch",
-      1, 1, ofctl_dump_tables },
+      1, 1, ofctl_dump_tables, OVS_RO },
     { "dump-table-features", "switch",
-      1, 1, ofctl_dump_table_features },
+      1, 1, ofctl_dump_table_features, OVS_RO },
     { "dump-table-desc", "switch",
-      1, 1, ofctl_dump_table_desc },
+      1, 1, ofctl_dump_table_desc, OVS_RO },
     { "dump-flows", "switch",
-      1, 2, ofctl_dump_flows },
+      1, 2, ofctl_dump_flows, OVS_RO },
     { "dump-aggregate", "switch",
-      1, 2, ofctl_dump_aggregate },
+      1, 2, ofctl_dump_aggregate, OVS_RO },
     { "queue-stats", "switch [port [queue]]",
-      1, 3, ofctl_queue_stats },
+      1, 3, ofctl_queue_stats, OVS_RO },
     { "queue-get-config", "switch [port [queue]]",
-      1, 3, ofctl_queue_get_config },
+      1, 3, ofctl_queue_get_config, OVS_RO },
     { "add-flow", "switch flow",
-      2, 2, ofctl_add_flow },
+      2, 2, ofctl_add_flow, OVS_RW },
     { "add-flows", "switch file",
-      2, 2, ofctl_add_flows },
+      2, 2, ofctl_add_flows, OVS_RW },
     { "mod-flows", "switch flow",
-      2, 2, ofctl_mod_flows },
+      2, 2, ofctl_mod_flows, OVS_RW },
     { "del-flows", "switch [flow]",
-      1, 2, ofctl_del_flows },
+      1, 2, ofctl_del_flows, OVS_RW },
     { "replace-flows", "switch file",
-      2, 2, ofctl_replace_flows },
+      2, 2, ofctl_replace_flows, OVS_RW },
     { "diff-flows", "source1 source2",
-      2, 2, ofctl_diff_flows },
+      2, 2, ofctl_diff_flows, OVS_RW },
     { "add-meter", "switch meter",
-      2, 2, ofctl_add_meter },
+      2, 2, ofctl_add_meter, OVS_RW },
     { "mod-meter", "switch meter",
-      2, 2, ofctl_mod_meter },
+      2, 2, ofctl_mod_meter, OVS_RW },
     { "del-meter", "switch meter",
-      2, 2, ofctl_del_meters },
+      2, 2, ofctl_del_meters, OVS_RW },
     { "del-meters", "switch",
-      1, 1, ofctl_del_meters },
+      1, 1, ofctl_del_meters, OVS_RW },
     { "dump-meter", "switch meter",
-      2, 2, ofctl_dump_meters },
+      2, 2, ofctl_dump_meters, OVS_RO },
     { "dump-meters", "switch",
-      1, 1, ofctl_dump_meters },
+      1, 1, ofctl_dump_meters, OVS_RO },
     { "meter-stats", "switch [meter]",
-      1, 2, ofctl_meter_stats },
+      1, 2, ofctl_meter_stats, OVS_RO },
     { "meter-features", "switch",
-      1, 1, ofctl_meter_features },
+      1, 1, ofctl_meter_features, OVS_RO },
     { "packet-out", "switch in_port actions packet...",
-      4, INT_MAX, ofctl_packet_out },
+      4, INT_MAX, ofctl_packet_out, OVS_RW },
     { "dump-ports", "switch [port]",
-      1, 2, ofctl_dump_ports },
+      1, 2, ofctl_dump_ports, OVS_RO },
     { "dump-ports-desc", "switch [port]",
-      1, 2, ofctl_dump_ports_desc },
+      1, 2, ofctl_dump_ports_desc, OVS_RO },
     { "mod-port", "switch iface act",
-      3, 3, ofctl_mod_port },
+      3, 3, ofctl_mod_port, OVS_RW },
     { "mod-table", "switch mod",
-      3, 3, ofctl_mod_table },
+      3, 3, ofctl_mod_table, OVS_RW },
     { "get-frags", "switch",
-      1, 1, ofctl_get_frags },
+      1, 1, ofctl_get_frags, OVS_RO },
     { "set-frags", "switch frag_mode",
-      2, 2, ofctl_set_frags },
+      2, 2, ofctl_set_frags, OVS_RW },
     { "probe", "target",
-      1, 1, ofctl_probe },
+      1, 1, ofctl_probe, OVS_RO },
     { "ping", "target [n]",
-      1, 2, ofctl_ping },
+      1, 2, ofctl_ping, OVS_RO },
     { "benchmark", "target n count",
-      3, 3, ofctl_benchmark },
+      3, 3, ofctl_benchmark, OVS_RO },

     { "dump-ipfix-bridge", "switch",
-      1, 1, ofctl_dump_ipfix_bridge},
+      1, 1, ofctl_dump_ipfix_bridge, OVS_RO },
     { "dump-ipfix-flow", "switch",
-      1, 1, ofctl_dump_ipfix_flow},
+      1, 1, ofctl_dump_ipfix_flow, OVS_RO },

     { "ofp-parse", "file",
-      1, 1, ofctl_ofp_parse },
+      1, 1, ofctl_ofp_parse, OVS_RW },
     { "ofp-parse-pcap", "pcap",
-      1, INT_MAX, ofctl_ofp_parse_pcap },
+      1, INT_MAX, ofctl_ofp_parse_pcap, OVS_RW },

     { "add-group", "switch group",
-      1, 2, ofctl_add_group },
+      1, 2, ofctl_add_group, OVS_RW },
     { "add-groups", "switch file",
-      1, 2, ofctl_add_groups },
+      1, 2, ofctl_add_groups, OVS_RW },
     { "mod-group", "switch group",
-      1, 2, ofctl_mod_group },
+      1, 2, ofctl_mod_group, OVS_RW },
     { "del-groups", "switch [group]",
-      1, 2, ofctl_del_groups },
+      1, 2, ofctl_del_groups, OVS_RW },
     { "insert-buckets", "switch [group]",
-      1, 2, ofctl_insert_bucket },
+      1, 2, ofctl_insert_bucket, OVS_RW },
     { "remove-buckets", "switch [group]",
-      1, 2, ofctl_remove_bucket },
+      1, 2, ofctl_remove_bucket, OVS_RW },
     { "dump-groups", "switch [group]",
-      1, 2, ofctl_dump_group_desc },
+      1, 2, ofctl_dump_group_desc, OVS_RO },
     { "dump-group-stats", "switch [group]",
-      1, 2, ofctl_dump_group_stats },
+      1, 2, ofctl_dump_group_stats, OVS_RO },
     { "dump-group-features", "switch",
-      1, 1, ofctl_dump_group_features },
+      1, 1, ofctl_dump_group_features, OVS_RO },

     { "bundle", "switch file",
-      2, 2, ofctl_bundle },
+      2, 2, ofctl_bundle, OVS_RW },

     { "add-tlv-map", "switch map",
-      2, 2, ofctl_add_tlv_map },
+      2, 2, ofctl_add_tlv_map, OVS_RO },
     { "del-tlv-map", "switch [map]",
-      1, 2, ofctl_del_tlv_map },
+      1, 2, ofctl_del_tlv_map, OVS_RO },
     { "dump-tlv-map", "switch",
-      1, 1, ofctl_dump_tlv_map },
-    { "help", NULL, 0, INT_MAX, ofctl_help },
-    { "list-commands", NULL, 0, INT_MAX, ofctl_list_commands },
+      1, 1, ofctl_dump_tlv_map, OVS_RO },
+    { "help", NULL, 0, INT_MAX, ofctl_help, OVS_RO },
+    { "list-commands", NULL, 0, INT_MAX, ofctl_list_commands, OVS_RO },

     /* Undocumented commands for testing. */
-    { "parse-flow", NULL, 1, 1, ofctl_parse_flow },
-    { "parse-flows", NULL, 1, 1, ofctl_parse_flows },
-    { "parse-nx-match", NULL, 0, 0, ofctl_parse_nxm },
-    { "parse-nxm", NULL, 0, 0, ofctl_parse_nxm },
-    { "parse-oxm", NULL, 1, 1, ofctl_parse_oxm },
-    { "parse-actions", NULL, 1, 1, ofctl_parse_actions },
-    { "parse-instructions", NULL, 1, 1, ofctl_parse_instructions },
-    { "parse-ofp10-match", NULL, 0, 0, ofctl_parse_ofp10_match },
-    { "parse-ofp11-match", NULL, 0, 0, ofctl_parse_ofp11_match },
-    { "parse-pcap", NULL, 1, INT_MAX, ofctl_parse_pcap },
-    { "check-vlan", NULL, 2, 2, ofctl_check_vlan },
-    { "print-error", NULL, 1, 1, ofctl_print_error },
-    { "encode-error-reply", NULL, 2, 2, ofctl_encode_error_reply },
-    { "ofp-print", NULL, 1, 2, ofctl_ofp_print },
-    { "encode-hello", NULL, 1, 1, ofctl_encode_hello },
-    { "parse-key-value", NULL, 1, INT_MAX, ofctl_parse_key_value },
-
-    { NULL, NULL, 0, 0, NULL },
+    { "parse-flow", NULL, 1, 1, ofctl_parse_flow, OVS_RW },
+    { "parse-flows", NULL, 1, 1, ofctl_parse_flows, OVS_RW },
+    { "parse-nx-match", NULL, 0, 0, ofctl_parse_nxm, OVS_RW },
+    { "parse-nxm", NULL, 0, 0, ofctl_parse_nxm, OVS_RW },
+    { "parse-oxm", NULL, 1, 1, ofctl_parse_oxm, OVS_RW },
+    { "parse-actions", NULL, 1, 1, ofctl_parse_actions, OVS_RW },
+    { "parse-instructions", NULL, 1, 1, ofctl_parse_instructions, OVS_RW },
+    { "parse-ofp10-match", NULL, 0, 0, ofctl_parse_ofp10_match, OVS_RW },
+    { "parse-ofp11-match", NULL, 0, 0, ofctl_parse_ofp11_match, OVS_RW },
+    { "parse-pcap", NULL, 1, INT_MAX, ofctl_parse_pcap, OVS_RW },
+    { "check-vlan", NULL, 2, 2, ofctl_check_vlan, OVS_RW },
+    { "print-error", NULL, 1, 1, ofctl_print_error, OVS_RW },
+    { "encode-error-reply", NULL, 2, 2, ofctl_encode_error_reply, OVS_RW },
+    { "ofp-print", NULL, 1, 2, ofctl_ofp_print, OVS_RW },
+    { "encode-hello", NULL, 1, 1, ofctl_encode_hello, OVS_RW },
+    { "parse-key-value", NULL, 1, INT_MAX, ofctl_parse_key_value, OVS_RW },
+
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };

 static const struct ovs_cmdl_command *get_all_commands(void)
---
 lib/command-line.c             |  51 +++++++++----
 lib/command-line.h             |   6 +-
 lib/db-ctl-base.h              |   2 +-
 lib/dpctl.c                    |  47 ++++++------
 lib/dpctl.h                    |   3 +
 ovsdb/ovsdb-tool.c             |  28 ++++----
 tests/ovstest.c                |   4 +-
 tests/test-bitmap.c            |   6 +-
 tests/test-ccmap.c             |   6 +-
 tests/test-classifier.c        |  28 ++++----
 tests/test-cmap.c              |   6 +-
 tests/test-conntrack.c         |   6 +-
 tests/test-heap.c              |  14 ++--
 tests/test-jsonrpc.c           |  10 +--
 tests/test-netlink-conntrack.c |   8 +--
 tests/test-ovn.c               |  26 +++----
 tests/test-ovsdb.c             |  80 ++++++++++-----------
 tests/test-reconnect.c         |  32 ++++-----
 tests/test-util.c              |  34 ++++-----
 tests/test-vconn.c             |  18 ++---
 utilities/ovs-dpctl.c          |   7 ++
 utilities/ovs-ofctl.8.in       |   3 +
 utilities/ovs-ofctl.c          | 160 ++++++++++++++++++++++-------------------
 23 files changed, 323 insertions(+), 262 deletions(-)

diff --git a/lib/command-line.c b/lib/command-line.c
index bda5ed6..92ce888 100644
--- a/lib/command-line.c
+++ b/lib/command-line.c
@@ -87,20 +87,11 @@ ovs_cmdl_print_options(const struct option options[])
     ds_destroy(&ds);
 }
 
-/* Runs the command designated by argv[0] within the command table specified by
- * 'commands', which must be terminated by a command whose 'name' member is a
- * null pointer.
- *
- * Command-line options should be stripped off, so that a typical invocation
- * looks like:
- *    struct ovs_cmdl_context ctx = {
- *        .argc = argc - optind,
- *        .argv = argv + optind,
- *    };
- *    ovs_cmdl_run_command(&ctx, my_commands);
- * */
-void
-ovs_cmdl_run_command(struct ovs_cmdl_context *ctx, const struct ovs_cmdl_command commands[])
+/* Whether to commit changes or not. */
+static bool read_only;
+
+static void
+_ovs_cmdl_run_command(struct ovs_cmdl_context *ctx, const struct ovs_cmdl_command commands[])
 {
     const struct ovs_cmdl_command *p;
 
@@ -118,6 +109,10 @@ ovs_cmdl_run_command(struct ovs_cmdl_context *ctx, const struct ovs_cmdl_command
                 VLOG_FATAL("'%s' command takes at most %d arguments",
                            p->name, p->max_args);
             } else {
+                if (p->mode == OVS_RW && read_only) {
+                    VLOG_FATAL("'%s' command does not work in read only mode",
+                               p->name);
+                }
                 p->handler(ctx);
                 if (ferror(stdout)) {
                     VLOG_FATAL("write to stdout failed");
@@ -132,6 +127,34 @@ ovs_cmdl_run_command(struct ovs_cmdl_context *ctx, const struct ovs_cmdl_command
 
     VLOG_FATAL("unknown command '%s'; use --help for help", ctx->argv[0]);
 }
+
+/* Runs the command designated by argv[0] within the command table specified by
+ * 'commands', which must be terminated by a command whose 'name' member is a
+ * null pointer.
+ *
+ * Command-line options should be stripped off, so that a typical invocation
+ * looks like:
+ *    struct ovs_cmdl_context ctx = {
+ *        .argc = argc - optind,
+ *        .argv = argv + optind,
+ *    };
+ *    ovs_cmdl_run_command(&ctx, my_commands);
+ * */
+void
+ovs_cmdl_run_command(struct ovs_cmdl_context *ctx,
+                     const struct ovs_cmdl_command commands[])
+{
+    read_only = false;
+    _ovs_cmdl_run_command(ctx, commands);
+}
+
+void
+ovs_cmdl_run_command_read_only(struct ovs_cmdl_context *ctx,
+                               const struct ovs_cmdl_command commands[])
+{
+    read_only = true;
+    _ovs_cmdl_run_command(ctx, commands);
+}
 
 /* Process title. */
 
diff --git a/lib/command-line.h b/lib/command-line.h
index e9e3b7b..00ace94 100644
--- a/lib/command-line.h
+++ b/lib/command-line.h
@@ -41,12 +41,16 @@ struct ovs_cmdl_command {
     int min_args;
     int max_args;
     ovs_cmdl_handler handler;
+    enum { OVS_RO, OVS_RW } mode;    /* Does this command modify things? */
 };
 
 char *ovs_cmdl_long_options_to_short_options(const struct option *options);
 void ovs_cmdl_print_options(const struct option *options);
 void ovs_cmdl_print_commands(const struct ovs_cmdl_command *commands);
-void ovs_cmdl_run_command(struct ovs_cmdl_context *, const struct ovs_cmdl_command[]);
+void ovs_cmdl_run_command(struct ovs_cmdl_context *,
+                          const struct ovs_cmdl_command[]);
+void ovs_cmdl_run_command_read_only(struct ovs_cmdl_context *,
+                                    const struct ovs_cmdl_command[]);
 
 void ovs_cmdl_proctitle_init(int argc, char **argv);
 #if defined(__FreeBSD__) || defined(__NetBSD__)
diff --git a/lib/db-ctl-base.h b/lib/db-ctl-base.h
index 0f4658e..e5a354d 100644
--- a/lib/db-ctl-base.h
+++ b/lib/db-ctl-base.h
@@ -120,7 +120,7 @@ struct ctl_command_syntax {
      * empty string if the command does not support any options. */
     const char *options;
 
-    enum { RO, RW } mode;       /* Does this command modify the database? */
+    enum { RO, RW } mode;   /* Does this command modify the database? */
 };
 
 /* A command extracted from command-line input plus the structs for
diff --git a/lib/dpctl.c b/lib/dpctl.c
index b470ab0..28f2f83 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -59,6 +59,7 @@ struct dpctl_command {
     int min_args;
     int max_args;
     dpctl_command_handler *handler;
+    enum { DP_RO, DP_RW} mode;
 };
 static const struct dpctl_command *get_all_dpctl_commands(void);
 static void dpctl_print(struct dpctl_params *dpctl_p, const char *fmt, ...)
@@ -1615,29 +1616,29 @@ out:
 }
 
 static const struct dpctl_command all_commands[] = {
-    { "add-dp", "add-dp dp [iface...]", 1, INT_MAX, dpctl_add_dp },
-    { "del-dp", "del-dp dp", 1, 1, dpctl_del_dp },
-    { "add-if", "add-if dp iface...", 2, INT_MAX, dpctl_add_if },
-    { "del-if", "del-if dp iface...", 2, INT_MAX, dpctl_del_if },
-    { "set-if", "set-if dp iface...", 2, INT_MAX, dpctl_set_if },
-    { "dump-dps", "", 0, 0, dpctl_dump_dps },
-    { "show", "[dp...]", 0, INT_MAX, dpctl_show },
-    { "dump-flows", "[dp]", 0, 2, dpctl_dump_flows },
-    { "add-flow", "add-flow [dp] flow actions", 2, 3, dpctl_add_flow },
-    { "mod-flow", "mod-flow [dp] flow actions", 2, 3, dpctl_mod_flow },
-    { "get-flow", "get-flow [dp] ufid", 1, 2, dpctl_get_flow },
-    { "del-flow", "del-flow [dp] flow", 1, 2, dpctl_del_flow },
-    { "del-flows", "[dp]", 0, 1, dpctl_del_flows },
-    { "dump-conntrack", "[dp] [zone=N]", 0, 2, dpctl_dump_conntrack },
-    { "flush-conntrack", "[dp] [zone=N]", 0, 2, dpctl_flush_conntrack },
-    { "help", "", 0, INT_MAX, dpctl_help },
-    { "list-commands", "", 0, INT_MAX, dpctl_list_commands },
+    { "add-dp", "add-dp dp [iface...]", 1, INT_MAX, dpctl_add_dp, DP_RW },
+    { "del-dp", "del-dp dp", 1, 1, dpctl_del_dp, DP_RW },
+    { "add-if", "add-if dp iface...", 2, INT_MAX, dpctl_add_if, DP_RW },
+    { "del-if", "del-if dp iface...", 2, INT_MAX, dpctl_del_if, DP_RW },
+    { "set-if", "set-if dp iface...", 2, INT_MAX, dpctl_set_if, DP_RW },
+    { "dump-dps", "", 0, 0, dpctl_dump_dps, DP_RO },
+    { "show", "[dp...]", 0, INT_MAX, dpctl_show, DP_RO },
+    { "dump-flows", "[dp]", 0, 2, dpctl_dump_flows, DP_RO },
+    { "add-flow", "add-flow [dp] flow actions", 2, 3, dpctl_add_flow, DP_RW },
+    { "mod-flow", "mod-flow [dp] flow actions", 2, 3, dpctl_mod_flow, DP_RW },
+    { "get-flow", "get-flow [dp] ufid", 1, 2, dpctl_get_flow, DP_RO },
+    { "del-flow", "del-flow [dp] flow", 1, 2, dpctl_del_flow, DP_RW },
+    { "del-flows", "[dp]", 0, 1, dpctl_del_flows, DP_RW },
+    { "dump-conntrack", "[dp] [zone=N]", 0, 2, dpctl_dump_conntrack, DP_RO },
+    { "flush-conntrack", "[dp] [zone=N]", 0, 2, dpctl_flush_conntrack, DP_RW },
+    { "help", "", 0, INT_MAX, dpctl_help, DP_RO },
+    { "list-commands", "", 0, INT_MAX, dpctl_list_commands, DP_RO },
 
     /* Undocumented commands for testing. */
-    { "parse-actions", "actions", 1, INT_MAX, dpctl_parse_actions },
-    { "normalize-actions", "actions", 2, INT_MAX, dpctl_normalize_actions },
+    { "parse-actions", "actions", 1, INT_MAX, dpctl_parse_actions, DP_RO },
+    { "normalize-actions", "actions", 2, INT_MAX, dpctl_normalize_actions, DP_RO },
 
-    { NULL, NULL, 0, 0, NULL },
+    { NULL, NULL, 0, 0, NULL, DP_RO },
 };
 
 static const struct dpctl_command *get_all_dpctl_commands(void)
@@ -1672,6 +1673,12 @@ dpctl_run_command(int argc, const char *argv[], struct dpctl_params *dpctl_p)
                             p->name, p->max_args);
                 return EINVAL;
             } else {
+                if (p->mode == DP_RW && dpctl_p->read_only) {
+                    dpctl_error(dpctl_p, 0,
+                                "'%s' command does not work in read only mode",
+                                p->name);
+                    return EINVAL;
+                }
                 return p->handler(argc, argv, dpctl_p);
             }
         }
diff --git a/lib/dpctl.h b/lib/dpctl.h
index 11a172d..4ee083f 100644
--- a/lib/dpctl.h
+++ b/lib/dpctl.h
@@ -33,6 +33,9 @@ struct dpctl_params {
     /* --may-create: Allow mod-flows command to create a new flow? */
     bool may_create;
 
+    /* --read-only: Do not run R/W commands? */
+    bool read_only;
+
     /* -m, --more: Increase output verbosity. */
     int verbosity;
 
diff --git a/ovsdb/ovsdb-tool.c b/ovsdb/ovsdb-tool.c
index af83da2..6531f6a 100644
--- a/ovsdb/ovsdb-tool.c
+++ b/ovsdb/ovsdb-tool.c
@@ -573,20 +573,20 @@ do_list_commands(struct ovs_cmdl_context *ctx OVS_UNUSED)
 }
 
 static const struct ovs_cmdl_command all_commands[] = {
-    { "create", "[db [schema]]", 0, 2, do_create },
-    { "compact", "[db [dst]]", 0, 2, do_compact },
-    { "convert", "[db [schema [dst]]]", 0, 3, do_convert },
-    { "needs-conversion", NULL, 0, 2, do_needs_conversion },
-    { "db-version", "[db]",  0, 1, do_db_version },
-    { "db-cksum", "[db]", 0, 1, do_db_cksum },
-    { "schema-version", "[schema]", 0, 1, do_schema_version },
-    { "schema-cksum", "[schema]", 0, 1, do_schema_cksum },
-    { "query", "[db] trns", 1, 2, do_query },
-    { "transact", "[db] trns", 1, 2, do_transact },
-    { "show-log", "[db]", 0, 1, do_show_log },
-    { "help", NULL, 0, INT_MAX, do_help },
-    { "list-commands", NULL, 0, INT_MAX, do_list_commands },
-    { NULL, NULL, 0, 0, NULL },
+    { "create", "[db [schema]]", 0, 2, do_create, OVS_RW },
+    { "compact", "[db [dst]]", 0, 2, do_compact, OVS_RW },
+    { "convert", "[db [schema [dst]]]", 0, 3, do_convert, OVS_RW },
+    { "needs-conversion", NULL, 0, 2, do_needs_conversion, OVS_RO },
+    { "db-version", "[db]",  0, 1, do_db_version, OVS_RO },
+    { "db-cksum", "[db]", 0, 1, do_db_cksum, OVS_RO },
+    { "schema-version", "[schema]", 0, 1, do_schema_version, OVS_RO },
+    { "schema-cksum", "[schema]", 0, 1, do_schema_cksum, OVS_RO },
+    { "query", "[db] trns", 1, 2, do_query, OVS_RO },
+    { "transact", "[db] trns", 1, 2, do_transact, OVS_RO },
+    { "show-log", "[db]", 0, 1, do_show_log, OVS_RO },
+    { "help", NULL, 0, INT_MAX, do_help, OVS_RO },
+    { "list-commands", NULL, 0, INT_MAX, do_list_commands, OVS_RO },
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };
 
 static const struct ovs_cmdl_command *get_all_commands(void)
diff --git a/tests/ovstest.c b/tests/ovstest.c
index 2fb7f66..745bd2e 100644
--- a/tests/ovstest.c
+++ b/tests/ovstest.c
@@ -33,7 +33,7 @@ static size_t allocated_commands = 0;
 static void
 add_command(struct ovs_cmdl_command *cmd)
 {
-    const struct ovs_cmdl_command nil = {NULL, NULL, 0, 0, NULL};
+    const struct ovs_cmdl_command nil = {NULL, NULL, 0, 0, NULL, OVS_RO};
 
     while (n_commands + 1 >= allocated_commands) {
         commands = x2nrealloc(commands, &allocated_commands,
@@ -86,7 +86,7 @@ help(struct ovs_cmdl_context *ctx OVS_UNUSED)
 static void
 add_top_level_commands(void)
 {
-    struct ovs_cmdl_command help_cmd = {"--help", NULL, 0, 0, help};
+    struct ovs_cmdl_command help_cmd = {"--help", NULL, 0, 0, help, OVS_RO };
 
     add_command(&help_cmd);
 }
diff --git a/tests/test-bitmap.c b/tests/test-bitmap.c
index 3dbc8df..484407b 100644
--- a/tests/test-bitmap.c
+++ b/tests/test-bitmap.c
@@ -149,9 +149,9 @@ run_benchmarks(struct ovs_cmdl_context *ctx)
 }
 
 static const struct ovs_cmdl_command commands[] = {
-    {"check", NULL, 0, 0, run_tests},
-    {"benchmark", NULL, 1, 1, run_benchmarks},
-    {NULL, NULL, 0, 0, NULL},
+    {"check", NULL, 0, 0, run_tests, OVS_RO},
+    {"benchmark", NULL, 1, 1, run_benchmarks, OVS_RO},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };
 
 static void
diff --git a/tests/test-ccmap.c b/tests/test-ccmap.c
index 4efe1b9..5ebc659 100644
--- a/tests/test-ccmap.c
+++ b/tests/test-ccmap.c
@@ -272,9 +272,9 @@ benchmark_ccmap(void)
 
 
 static const struct ovs_cmdl_command commands[] = {
-    {"check", NULL, 0, 1, run_tests},
-    {"benchmark", NULL, 3, 3, run_benchmarks},
-    {NULL, NULL, 0, 0, NULL},
+    {"check", NULL, 0, 1, run_tests, OVS_RO},
+    {"benchmark", NULL, 3, 3, run_benchmarks, OVS_RO},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };
 
 static void
diff --git a/tests/test-classifier.c b/tests/test-classifier.c
index 3a275b4..f85ea4f 100644
--- a/tests/test-classifier.c
+++ b/tests/test-classifier.c
@@ -1838,23 +1838,23 @@ static void help(struct ovs_cmdl_context *ctx);
 
 static const struct ovs_cmdl_command commands[] = {
     /* Classifier tests. */
-    {"empty", NULL, 0, 0, test_empty},
-    {"destroy-null", NULL, 0, 0, test_destroy_null},
-    {"single-rule", NULL, 0, 0, test_single_rule},
-    {"rule-replacement", NULL, 0, 0, test_rule_replacement},
-    {"many-rules-in-one-list", NULL, 0, 1, test_many_rules_in_one_list},
-    {"many-rules-in-one-table", NULL, 0, 1, test_many_rules_in_one_table},
-    {"many-rules-in-two-tables", NULL, 0, 0, test_many_rules_in_two_tables},
-    {"many-rules-in-five-tables", NULL, 0, 0, test_many_rules_in_five_tables},
-    {"benchmark", NULL, 0, 5, run_benchmarks},
+    {"empty", NULL, 0, 0, test_empty, OVS_RO },
+    {"destroy-null", NULL, 0, 0, test_destroy_null, OVS_RO },
+    {"single-rule", NULL, 0, 0, test_single_rule, OVS_RO },
+    {"rule-replacement", NULL, 0, 0, test_rule_replacement, OVS_RO },
+    {"many-rules-in-one-list", NULL, 0, 1, test_many_rules_in_one_list, OVS_RO },
+    {"many-rules-in-one-table", NULL, 0, 1, test_many_rules_in_one_table, OVS_RO },
+    {"many-rules-in-two-tables", NULL, 0, 0, test_many_rules_in_two_tables, OVS_RO },
+    {"many-rules-in-five-tables", NULL, 0, 0, test_many_rules_in_five_tables, OVS_RO },
+    {"benchmark", NULL, 0, 5, run_benchmarks, OVS_RO },
 
     /* Miniflow and minimask tests. */
-    {"miniflow", NULL, 0, 0, test_miniflow},
-    {"minimask_has_extra", NULL, 0, 0, test_minimask_has_extra},
-    {"minimask_combine", NULL, 0, 0, test_minimask_combine},
+    {"miniflow", NULL, 0, 0, test_miniflow, OVS_RO },
+    {"minimask_has_extra", NULL, 0, 0, test_minimask_has_extra, OVS_RO },
+    {"minimask_combine", NULL, 0, 0, test_minimask_combine, OVS_RO },
 
-    {"--help", NULL, 0, 0, help},
-    {NULL, NULL, 0, 0, NULL},
+    {"--help", NULL, 0, 0, help, OVS_RO },
+    {NULL, NULL, 0, 0, NULL, OVS_RO },
 };
 
 static void
diff --git a/tests/test-cmap.c b/tests/test-cmap.c
index 4cac7de..e159a16 100644
--- a/tests/test-cmap.c
+++ b/tests/test-cmap.c
@@ -636,9 +636,9 @@ benchmark_hmap(void)
 }
 
 static const struct ovs_cmdl_command commands[] = {
-    {"check", NULL, 0, 1, run_tests},
-    {"benchmark", NULL, 3, 4, run_benchmarks},
-    {NULL, NULL, 0, 0, NULL},
+    {"check", NULL, 0, 1, run_tests, OVS_RO},
+    {"benchmark", NULL, 3, 4, run_benchmarks, OVS_RO},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };
 
 static void
diff --git a/tests/test-conntrack.c b/tests/test-conntrack.c
index 6c1b373..803e2b9 100644
--- a/tests/test-conntrack.c
+++ b/tests/test-conntrack.c
@@ -260,13 +260,13 @@ static const struct ovs_cmdl_command commands[] = {
      * is '1', each packet in a batch will have a different source and
      * destination port */
     {"benchmark", "n_threads n_pkts batch_size [change_connection]", 3, 4,
-     test_benchmark},
+     test_benchmark, OVS_RO},
     /* Reads packets from 'file' and sends them to the connection tracker,
      * 'batch_size' (1 by default) per call, with the commit flag set.
      * Prints the ct_state of each packet. */
-    {"pcap", "file [batch_size]", 1, 2, test_pcap},
+    {"pcap", "file [batch_size]", 1, 2, test_pcap, OVS_RO},
 
-    {NULL, NULL, 0, 0, NULL},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };
 
 static void
diff --git a/tests/test-heap.c b/tests/test-heap.c
index 6dab22b..88c9f25 100644
--- a/tests/test-heap.c
+++ b/tests/test-heap.c
@@ -461,16 +461,16 @@ test_heap_raw_delete(struct ovs_cmdl_context *ctx OVS_UNUSED)
 
 static const struct ovs_cmdl_command commands[] = {
     { "insert-delete-same-order", NULL, 0, 0,
-      test_heap_insert_delete_same_order, },
+      test_heap_insert_delete_same_order, OVS_RO },
     { "insert-delete-reverse-order", NULL, 0, 0,
-      test_heap_insert_delete_reverse_order, },
+      test_heap_insert_delete_reverse_order, OVS_RO },
     { "insert-delete-every-order", NULL, 0, 0,
-      test_heap_insert_delete_every_order, },
+      test_heap_insert_delete_every_order, OVS_RO },
     { "insert-delete-same-order-with-dups", NULL, 0, 0,
-      test_heap_insert_delete_same_order_with_dups, },
-    { "raw-insert", NULL, 0, 0, test_heap_raw_insert, },
-    { "raw-delete", NULL, 0, 0, test_heap_raw_delete, },
-    { NULL, NULL, 0, 0, NULL, },
+      test_heap_insert_delete_same_order_with_dups, OVS_RO },
+    { "raw-insert", NULL, 0, 0, test_heap_raw_insert, OVS_RO },
+    { "raw-delete", NULL, 0, 0, test_heap_raw_delete, OVS_RO },
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };
 
 static void
diff --git a/tests/test-jsonrpc.c b/tests/test-jsonrpc.c
index be79064..684601a 100644
--- a/tests/test-jsonrpc.c
+++ b/tests/test-jsonrpc.c
@@ -331,11 +331,11 @@ do_help(struct ovs_cmdl_context *ctx OVS_UNUSED)
 }
 
 static struct ovs_cmdl_command all_commands[] = {
-    { "listen", NULL, 1, 1, do_listen },
-    { "request", NULL, 3, 3, do_request },
-    { "notify", NULL, 3, 3, do_notify },
-    { "help", NULL, 0, INT_MAX, do_help },
-    { NULL, NULL, 0, 0, NULL },
+    { "listen", NULL, 1, 1, do_listen, OVS_RO },
+    { "request", NULL, 3, 3, do_request, OVS_RO },
+    { "notify", NULL, 3, 3, do_notify, OVS_RO },
+    { "help", NULL, 0, INT_MAX, do_help, OVS_RO },
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };
 
 static struct ovs_cmdl_command *
diff --git a/tests/test-netlink-conntrack.c b/tests/test-netlink-conntrack.c
index f0d48f7..000062d 100644
--- a/tests/test-netlink-conntrack.c
+++ b/tests/test-netlink-conntrack.c
@@ -161,14 +161,14 @@ static const struct ovs_cmdl_command commands[] = {
     /* Linux netlink connection tracker interface test. */
 
     /* Prints all the entries in the connection table and exits. */
-    {"dump", "[zone=zone]", 0, 1, test_nl_ct_dump},
+    {"dump", "[zone=zone]", 0, 1, test_nl_ct_dump, OVS_RO},
     /* Listens to all the connection tracking events and prints them to
      * standard output until killed. */
-    {"monitor", "", 0, 0, test_nl_ct_monitor},
+    {"monitor", "", 0, 0, test_nl_ct_monitor, OVS_RO},
     /* Flushes all the entries from all the tables.. */
-    {"flush", "[zone=zone]", 0, 1, test_nl_ct_flush},
+    {"flush", "[zone=zone]", 0, 1, test_nl_ct_flush, OVS_RO},
 
-    {NULL, NULL, 0, 0, NULL},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };
 
 static void
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index b5fca56..75af537 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -1470,26 +1470,26 @@ test_ovn_main(int argc, char *argv[])
 
     static const struct ovs_cmdl_command commands[] = {
         /* Lexer. */
-        {"lex", NULL, 0, 0, test_lex},
+        {"lex", NULL, 0, 0, test_lex, OVS_RO},
 
         /* Symbol table. */
-        {"dump-symtab", NULL, 0, 0, test_dump_symtab},
+        {"dump-symtab", NULL, 0, 0, test_dump_symtab, OVS_RO},
 
         /* Expressions. */
-        {"parse-expr", NULL, 0, 0, test_parse_expr},
-        {"annotate-expr", NULL, 0, 0, test_annotate_expr},
-        {"simplify-expr", NULL, 0, 0, test_simplify_expr},
-        {"normalize-expr", NULL, 0, 0, test_normalize_expr},
-        {"expr-to-flows", NULL, 0, 0, test_expr_to_flows},
-        {"evaluate-expr", NULL, 1, 1, test_evaluate_expr},
-        {"composition", NULL, 1, 1, test_composition},
-        {"tree-shape", NULL, 1, 1, test_tree_shape},
-        {"exhaustive", NULL, 1, 1, test_exhaustive},
+        {"parse-expr", NULL, 0, 0, test_parse_expr, OVS_RO},
+        {"annotate-expr", NULL, 0, 0, test_annotate_expr, OVS_RO},
+        {"simplify-expr", NULL, 0, 0, test_simplify_expr, OVS_RO},
+        {"normalize-expr", NULL, 0, 0, test_normalize_expr, OVS_RO},
+        {"expr-to-flows", NULL, 0, 0, test_expr_to_flows, OVS_RO},
+        {"evaluate-expr", NULL, 1, 1, test_evaluate_expr, OVS_RO},
+        {"composition", NULL, 1, 1, test_composition, OVS_RO},
+        {"tree-shape", NULL, 1, 1, test_tree_shape, OVS_RO},
+        {"exhaustive", NULL, 1, 1, test_exhaustive, OVS_RO},
 
         /* Actions. */
-        {"parse-actions", NULL, 0, 0, test_parse_actions},
+        {"parse-actions", NULL, 0, 0, test_parse_actions, OVS_RO},
 
-        {NULL, NULL, 0, 0, NULL},
+        {NULL, NULL, 0, 0, NULL, OVS_RO},
     };
     struct ovs_cmdl_context ctx;
     ctx.argc = argc - optind;
diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c
index e887f0f..8beb8d2 100644
--- a/tests/test-ovsdb.c
+++ b/tests/test-ovsdb.c
@@ -1671,13 +1671,13 @@ static void
 do_transact(struct ovs_cmdl_context *ctx)
 {
     static const struct ovs_cmdl_command do_transact_commands[] = {
-        { "commit", NULL, 0, 0, do_transact_commit },
-        { "abort", NULL, 0, 0, do_transact_abort },
-        { "insert", NULL, 2, 3, do_transact_insert },
-        { "delete", NULL, 1, 1, do_transact_delete },
-        { "modify", NULL, 2, 3, do_transact_modify },
-        { "print", NULL, 0, 0, do_transact_print },
-        { NULL, NULL, 0, 0, NULL },
+        { "commit", NULL, 0, 0, do_transact_commit, OVS_RO },
+        { "abort", NULL, 0, 0, do_transact_abort, OVS_RO },
+        { "insert", NULL, 2, 3, do_transact_insert, OVS_RO },
+        { "delete", NULL, 1, 1, do_transact_delete, OVS_RO },
+        { "modify", NULL, 2, 3, do_transact_modify, OVS_RO },
+        { "print", NULL, 0, 0, do_transact_print, OVS_RO },
+        { NULL, NULL, 0, 0, NULL, OVS_RO },
     };
 
     struct ovsdb_schema *schema;
@@ -2702,41 +2702,41 @@ do_idl_partial_update_set_column(struct ovs_cmdl_context *ctx)
 }
 
 static struct ovs_cmdl_command all_commands[] = {
-    { "log-io", NULL, 2, INT_MAX, do_log_io },
-    { "default-atoms", NULL, 0, 0, do_default_atoms },
-    { "default-data", NULL, 0, 0, do_default_data },
-    { "diff-data", NULL, 3, INT_MAX, do_diff_data},
-    { "parse-atomic-type", NULL, 1, 1, do_parse_atomic_type },
-    { "parse-base-type", NULL, 1, 1, do_parse_base_type },
-    { "parse-type", NULL, 1, 1, do_parse_type },
-    { "parse-atoms", NULL, 2, INT_MAX, do_parse_atoms },
-    { "parse-atom-strings", NULL, 2, INT_MAX, do_parse_atom_strings },
-    { "parse-data", NULL, 2, INT_MAX, do_parse_data },
-    { "parse-data-strings", NULL, 2, INT_MAX, do_parse_data_strings },
-    { "sort-atoms", NULL, 2, 2, do_sort_atoms },
-    { "parse-column", NULL, 2, 2, do_parse_column },
-    { "parse-table", NULL, 2, 3, do_parse_table },
-    { "parse-rows", NULL, 2, INT_MAX, do_parse_rows },
-    { "compare-rows", NULL, 2, INT_MAX, do_compare_rows },
-    { "parse-conditions", NULL, 2, INT_MAX, do_parse_conditions },
-    { "evaluate-conditions", NULL, 3, 3, do_evaluate_conditions },
-    { "evaluate-conditions-any", NULL, 3, 3, do_evaluate_conditions_any },
-    { "compare-conditions", NULL, 2, 2, do_compare_conditions },
-    { "parse-mutations", NULL, 2, INT_MAX, do_parse_mutations },
-    { "execute-mutations", NULL, 3, 3, do_execute_mutations },
-    { "query", NULL, 3, 3, do_query },
-    { "query-distinct", NULL, 4, 4, do_query_distinct },
-    { "transact", NULL, 1, INT_MAX, do_transact },
-    { "parse-schema", NULL, 1, 1, do_parse_schema },
-    { "execute", NULL, 2, INT_MAX, do_execute },
-    { "trigger", NULL, 2, INT_MAX, do_trigger },
-    { "idl", NULL, 1, INT_MAX, do_idl },
+    { "log-io", NULL, 2, INT_MAX, do_log_io, OVS_RO },
+    { "default-atoms", NULL, 0, 0, do_default_atoms, OVS_RO },
+    { "default-data", NULL, 0, 0, do_default_data, OVS_RO },
+    { "diff-data", NULL, 3, INT_MAX, do_diff_data, OVS_RO },
+    { "parse-atomic-type", NULL, 1, 1, do_parse_atomic_type, OVS_RO },
+    { "parse-base-type", NULL, 1, 1, do_parse_base_type, OVS_RO },
+    { "parse-type", NULL, 1, 1, do_parse_type, OVS_RO },
+    { "parse-atoms", NULL, 2, INT_MAX, do_parse_atoms, OVS_RO },
+    { "parse-atom-strings", NULL, 2, INT_MAX, do_parse_atom_strings, OVS_RO },
+    { "parse-data", NULL, 2, INT_MAX, do_parse_data, OVS_RO },
+    { "parse-data-strings", NULL, 2, INT_MAX, do_parse_data_strings, OVS_RO },
+    { "sort-atoms", NULL, 2, 2, do_sort_atoms, OVS_RO },
+    { "parse-column", NULL, 2, 2, do_parse_column, OVS_RO },
+    { "parse-table", NULL, 2, 3, do_parse_table, OVS_RO },
+    { "parse-rows", NULL, 2, INT_MAX, do_parse_rows, OVS_RO },
+    { "compare-rows", NULL, 2, INT_MAX, do_compare_rows, OVS_RO },
+    { "parse-conditions", NULL, 2, INT_MAX, do_parse_conditions, OVS_RO },
+    { "evaluate-conditions", NULL, 3, 3, do_evaluate_conditions, OVS_RO },
+    { "evaluate-conditions-any", NULL, 3, 3, do_evaluate_conditions_any, OVS_RO },
+    { "compare-conditions", NULL, 2, 2, do_compare_conditions, OVS_RO },
+    { "parse-mutations", NULL, 2, INT_MAX, do_parse_mutations, OVS_RO },
+    { "execute-mutations", NULL, 3, 3, do_execute_mutations, OVS_RO },
+    { "query", NULL, 3, 3, do_query, OVS_RO },
+    { "query-distinct", NULL, 4, 4, do_query_distinct, OVS_RO },
+    { "transact", NULL, 1, INT_MAX, do_transact, OVS_RO },
+    { "parse-schema", NULL, 1, 1, do_parse_schema, OVS_RO },
+    { "execute", NULL, 2, INT_MAX, do_execute, OVS_RO },
+    { "trigger", NULL, 2, INT_MAX, do_trigger, OVS_RO },
+    { "idl", NULL, 1, INT_MAX, do_idl, OVS_RO },
     { "idl-partial-update-map-column", NULL, 1, INT_MAX,
-                                       do_idl_partial_update_map_column },
+        do_idl_partial_update_map_column, OVS_RO },
     { "idl-partial-update-set-column", NULL, 1, INT_MAX,
-                                       do_idl_partial_update_set_column },
-    { "help", NULL, 0, INT_MAX, do_help },
-    { NULL, NULL, 0, 0, NULL },
+        do_idl_partial_update_set_column, OVS_RO },
+    { "help", NULL, 0, INT_MAX, do_help, OVS_RO },
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };
 
 static struct ovs_cmdl_command *
diff --git a/tests/test-reconnect.c b/tests/test-reconnect.c
index 76e43ea..72252b8 100644
--- a/tests/test-reconnect.c
+++ b/tests/test-reconnect.c
@@ -272,22 +272,22 @@ do_listen_error(struct ovs_cmdl_context *ctx)
 }
 
 static const struct ovs_cmdl_command all_commands[] = {
-    { "enable", NULL, 0, 0, do_enable },
-    { "disable", NULL, 0, 0, do_disable },
-    { "force-reconnect", NULL, 0, 0, do_force_reconnect },
-    { "disconnected", NULL, 0, 1, do_disconnected },
-    { "connecting", NULL, 0, 0, do_connecting },
-    { "connect-failed", NULL, 0, 1, do_connect_failed },
-    { "connected", NULL, 0, 0, do_connected },
-    { "activity", NULL, 0, 0, do_activity },
-    { "run", NULL, 0, 1, do_run },
-    { "advance", NULL, 1, 1, do_advance },
-    { "timeout", NULL, 0, 0, do_timeout },
-    { "set-max-tries", NULL, 1, 1, do_set_max_tries },
-    { "passive", NULL, 0, 0, do_set_passive },
-    { "listening", NULL, 0, 0, do_listening },
-    { "listen-error", NULL, 1, 1, do_listen_error },
-    { NULL, NULL, 0, 0, NULL },
+    { "enable", NULL, 0, 0, do_enable, OVS_RO },
+    { "disable", NULL, 0, 0, do_disable, OVS_RO },
+    { "force-reconnect", NULL, 0, 0, do_force_reconnect, OVS_RO },
+    { "disconnected", NULL, 0, 1, do_disconnected, OVS_RO },
+    { "connecting", NULL, 0, 0, do_connecting, OVS_RO },
+    { "connect-failed", NULL, 0, 1, do_connect_failed, OVS_RO },
+    { "connected", NULL, 0, 0, do_connected, OVS_RO },
+    { "activity", NULL, 0, 0, do_activity, OVS_RO },
+    { "run", NULL, 0, 1, do_run, OVS_RO },
+    { "advance", NULL, 1, 1, do_advance, OVS_RO },
+    { "timeout", NULL, 0, 0, do_timeout, OVS_RO },
+    { "set-max-tries", NULL, 1, 1, do_set_max_tries, OVS_RO },
+    { "passive", NULL, 0, 0, do_set_passive, OVS_RO },
+    { "listening", NULL, 0, 0, do_listening, OVS_RO },
+    { "listen-error", NULL, 1, 1, do_listen_error, OVS_RO },
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };
 
 static const struct ovs_cmdl_command *
diff --git a/tests/test-util.c b/tests/test-util.c
index ef45903..e37c722 100644
--- a/tests/test-util.c
+++ b/tests/test-util.c
@@ -1149,25 +1149,25 @@ test_file_name(struct ovs_cmdl_context *ctx)
 #endif /* _WIN32 */
 
 static const struct ovs_cmdl_command commands[] = {
-    {"ctz", NULL, 0, 0, test_ctz},
-    {"clz", NULL, 0, 0, test_clz},
-    {"round_up_pow2", NULL, 0, 0, test_round_up_pow2},
-    {"round_down_pow2", NULL, 0, 0, test_round_down_pow2},
-    {"count_1bits", NULL, 0, 0, test_count_1bits},
-    {"log_2_floor", NULL, 0, 0, test_log_2_floor},
-    {"bitwise_copy", NULL, 0, 0, test_bitwise_copy},
-    {"bitwise_zero", NULL, 0, 0, test_bitwise_zero},
-    {"bitwise_one", NULL, 0, 0, test_bitwise_one},
-    {"bitwise_is_all_zeros", NULL, 0, 0, test_bitwise_is_all_zeros},
-    {"bitwise_rscan", NULL, 0, 0, test_bitwise_rscan},
-    {"follow-symlinks", NULL, 1, INT_MAX, test_follow_symlinks},
-    {"assert", NULL, 0, 0, test_assert},
-    {"ovs_scan", NULL, 0, 0, test_ovs_scan},
-    {"snprintf", NULL, 0, 0, test_snprintf},
+    {"ctz", NULL, 0, 0, test_ctz, OVS_RO},
+    {"clz", NULL, 0, 0, test_clz, OVS_RO},
+    {"round_up_pow2", NULL, 0, 0, test_round_up_pow2, OVS_RO},
+    {"round_down_pow2", NULL, 0, 0, test_round_down_pow2, OVS_RO},
+    {"count_1bits", NULL, 0, 0, test_count_1bits, OVS_RO},
+    {"log_2_floor", NULL, 0, 0, test_log_2_floor, OVS_RO},
+    {"bitwise_copy", NULL, 0, 0, test_bitwise_copy, OVS_RO},
+    {"bitwise_zero", NULL, 0, 0, test_bitwise_zero, OVS_RO},
+    {"bitwise_one", NULL, 0, 0, test_bitwise_one, OVS_RO},
+    {"bitwise_is_all_zeros", NULL, 0, 0, test_bitwise_is_all_zeros, OVS_RO},
+    {"bitwise_rscan", NULL, 0, 0, test_bitwise_rscan, OVS_RO},
+    {"follow-symlinks", NULL, 1, INT_MAX, test_follow_symlinks, OVS_RO},
+    {"assert", NULL, 0, 0, test_assert, OVS_RO},
+    {"ovs_scan", NULL, 0, 0, test_ovs_scan, OVS_RO},
+    {"snprintf", NULL, 0, 0, test_snprintf, OVS_RO},
 #ifndef _WIN32
-    {"file_name", NULL, 1, INT_MAX, test_file_name},
+    {"file_name", NULL, 1, INT_MAX, test_file_name, OVS_RO},
 #endif
-    {NULL, NULL, 0, 0, NULL},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };
 
 static void
diff --git a/tests/test-vconn.c b/tests/test-vconn.c
index faa824a..1bf9aa0 100644
--- a/tests/test-vconn.c
+++ b/tests/test-vconn.c
@@ -432,15 +432,15 @@ test_send_invalid_version_hello(struct ovs_cmdl_context *ctx)
 }
 
 static const struct ovs_cmdl_command commands[] = {
-    {"refuse-connection", NULL, 1, 1, test_refuse_connection},
-    {"accept-then-close", NULL, 1, 1, test_accept_then_close},
-    {"read-hello", NULL, 1, 1, test_read_hello},
-    {"send-plain-hello", NULL, 1, 1, test_send_plain_hello},
-    {"send-long-hello", NULL, 1, 1, test_send_long_hello},
-    {"send-echo-hello", NULL, 1, 1, test_send_echo_hello},
-    {"send-short-hello", NULL, 1, 1, test_send_short_hello},
-    {"send-invalid-version-hello", NULL, 1, 1, test_send_invalid_version_hello},
-    {NULL, NULL, 0, 0, NULL},
+    {"refuse-connection", NULL, 1, 1, test_refuse_connection, OVS_RO},
+    {"accept-then-close", NULL, 1, 1, test_accept_then_close, OVS_RO},
+    {"read-hello", NULL, 1, 1, test_read_hello, OVS_RO},
+    {"send-plain-hello", NULL, 1, 1, test_send_plain_hello, OVS_RO},
+    {"send-long-hello", NULL, 1, 1, test_send_long_hello, OVS_RO},
+    {"send-echo-hello", NULL, 1, 1, test_send_echo_hello, OVS_RO},
+    {"send-short-hello", NULL, 1, 1, test_send_short_hello, OVS_RO},
+    {"send-invalid-version-hello", NULL, 1, 1, test_send_invalid_version_hello, OVS_RO},
+    {NULL, NULL, 0, 0, NULL, OVS_RO},
 };
 
 static void
diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
index 4897ea3..843d305 100644
--- a/utilities/ovs-dpctl.c
+++ b/utilities/ovs-dpctl.c
@@ -77,12 +77,14 @@ parse_options(int argc, char *argv[])
     enum {
         OPT_CLEAR = UCHAR_MAX + 1,
         OPT_MAY_CREATE,
+        OPT_READ_ONLY,
         VLOG_OPTION_ENUMS
     };
     static const struct option long_options[] = {
         {"statistics", no_argument, NULL, 's'},
         {"clear", no_argument, NULL, OPT_CLEAR},
         {"may-create", no_argument, NULL, OPT_MAY_CREATE},
+        {"read-only", no_argument, NULL, OPT_READ_ONLY},
         {"more", no_argument, NULL, 'm'},
         {"timeout", required_argument, NULL, 't'},
         {"help", no_argument, NULL, 'h'},
@@ -115,6 +117,10 @@ parse_options(int argc, char *argv[])
             dpctl_p.may_create = true;
             break;
 
+        case OPT_READ_ONLY:
+            dpctl_p.read_only = true;
+            break;
+
         case 'm':
             dpctl_p.verbosity++;
             break;
@@ -186,6 +192,7 @@ usage(void *userdata OVS_UNUSED)
            "  -m, --more                  increase verbosity of output\n"
            "\nOptions for mod-flow:\n"
            "  --may-create                create flow if it doesn't exist\n"
+           "  --read-only                 do not run read/write commands\n"
            "  --clear                     reset existing stats to zero\n"
            "\nOther options:\n"
            "  -t, --timeout=SECS          give up after SECS seconds\n"
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index cccd265..b56e5b3 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -2975,6 +2975,9 @@ depending on its configuration.
 \fB\-\-strict\fR
 Uses strict matching when running flow modification commands.
 .
+.IP "\fB\-\-read-only\fR"
+Do not execute read/write commands.
+.
 .IP "\fB\-\-bundle\fR"
 Execute flow mods as an OpenFlow 1.4 atomic bundle transaction.
 .RS
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index 42d358f..6fd3818 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -79,6 +79,9 @@ static bool bundle = false;
 /* --color: Use color markers. */
 static bool enable_color;
 
+/* --read-only: Do not execute read only commands. */
+static bool read_only;
+
 /* --strict: Use strict matching for flow mod commands?  Additionally governs
  * use of nx_pull_match() instead of nx_pull_match_loose() in parse-nx-match.
  */
@@ -142,7 +145,11 @@ main(int argc, char *argv[])
     ctx.argv = argv + optind;
 
     daemon_become_new_user(false);
-    ovs_cmdl_run_command(&ctx, get_all_commands());
+    if (read_only) {
+        ovs_cmdl_run_command_read_only(&ctx, get_all_commands());
+    } else {
+        ovs_cmdl_run_command(&ctx, get_all_commands());
+    }
     return 0;
 }
 
@@ -180,6 +187,7 @@ parse_options(int argc, char *argv[])
         OPT_BUNDLE,
         OPT_COLOR,
         OPT_MAY_CREATE,
+        OPT_READ_ONLY,
         DAEMON_OPTION_ENUMS,
         OFP_VERSION_OPTION_ENUMS,
         VLOG_OPTION_ENUMS
@@ -200,6 +208,7 @@ parse_options(int argc, char *argv[])
         {"bundle", no_argument, NULL, OPT_BUNDLE},
         {"color", optional_argument, NULL, OPT_COLOR},
         {"may-create", no_argument, NULL, OPT_MAY_CREATE},
+        {"read-only", no_argument, NULL, OPT_READ_ONLY},
         DAEMON_LONG_OPTIONS,
         OFP_VERSION_LONG_OPTIONS,
         VLOG_LONG_OPTIONS,
@@ -281,6 +290,10 @@ parse_options(int argc, char *argv[])
             strict = true;
             break;
 
+        case OPT_READ_ONLY:
+            read_only = true;
+            break;
+
         case OPT_READD:
             readd = true;
             break;
@@ -452,6 +465,7 @@ usage(void)
     vlog_usage();
     printf("\nOther options:\n"
            "  --strict                    use strict match for flow commands\n"
+           "  --read-only                 do not execute read/write commands\n"
            "  --readd                     replace flows that haven't changed\n"
            "  -F, --flow-format=FORMAT    force particular flow format\n"
            "  -P, --packet-in-format=FRMT force particular packet in format\n"
@@ -4120,136 +4134,136 @@ ofctl_parse_key_value(struct ovs_cmdl_context *ctx)
 
 static const struct ovs_cmdl_command all_commands[] = {
     { "show", "switch",
-      1, 1, ofctl_show },
+      1, 1, ofctl_show, OVS_RO },
     { "monitor", "switch [misslen] [invalid_ttl] [watch:[...]]",
-      1, 3, ofctl_monitor },
+      1, 3, ofctl_monitor, OVS_RO },
     { "snoop", "switch",
-      1, 1, ofctl_snoop },
+      1, 1, ofctl_snoop, OVS_RO },
     { "dump-desc", "switch",
-      1, 1, ofctl_dump_desc },
+      1, 1, ofctl_dump_desc, OVS_RO },
     { "dump-tables", "switch",
-      1, 1, ofctl_dump_tables },
+      1, 1, ofctl_dump_tables, OVS_RO },
     { "dump-table-features", "switch",
-      1, 1, ofctl_dump_table_features },
+      1, 1, ofctl_dump_table_features, OVS_RO },
     { "dump-table-desc", "switch",
-      1, 1, ofctl_dump_table_desc },
+      1, 1, ofctl_dump_table_desc, OVS_RO },
     { "dump-flows", "switch",
-      1, 2, ofctl_dump_flows },
+      1, 2, ofctl_dump_flows, OVS_RO },
     { "dump-aggregate", "switch",
-      1, 2, ofctl_dump_aggregate },
+      1, 2, ofctl_dump_aggregate, OVS_RO },
     { "queue-stats", "switch [port [queue]]",
-      1, 3, ofctl_queue_stats },
+      1, 3, ofctl_queue_stats, OVS_RO },
     { "queue-get-config", "switch [port [queue]]",
-      1, 3, ofctl_queue_get_config },
+      1, 3, ofctl_queue_get_config, OVS_RO },
     { "add-flow", "switch flow",
-      2, 2, ofctl_add_flow },
+      2, 2, ofctl_add_flow, OVS_RW },
     { "add-flows", "switch file",
-      2, 2, ofctl_add_flows },
+      2, 2, ofctl_add_flows, OVS_RW },
     { "mod-flows", "switch flow",
-      2, 2, ofctl_mod_flows },
+      2, 2, ofctl_mod_flows, OVS_RW },
     { "del-flows", "switch [flow]",
-      1, 2, ofctl_del_flows },
+      1, 2, ofctl_del_flows, OVS_RW },
     { "replace-flows", "switch file",
-      2, 2, ofctl_replace_flows },
+      2, 2, ofctl_replace_flows, OVS_RW },
     { "diff-flows", "source1 source2",
-      2, 2, ofctl_diff_flows },
+      2, 2, ofctl_diff_flows, OVS_RW },
     { "add-meter", "switch meter",
-      2, 2, ofctl_add_meter },
+      2, 2, ofctl_add_meter, OVS_RW },
     { "mod-meter", "switch meter",
-      2, 2, ofctl_mod_meter },
+      2, 2, ofctl_mod_meter, OVS_RW },
     { "del-meter", "switch meter",
-      2, 2, ofctl_del_meters },
+      2, 2, ofctl_del_meters, OVS_RW },
     { "del-meters", "switch",
-      1, 1, ofctl_del_meters },
+      1, 1, ofctl_del_meters, OVS_RW },
     { "dump-meter", "switch meter",
-      2, 2, ofctl_dump_meters },
+      2, 2, ofctl_dump_meters, OVS_RO },
     { "dump-meters", "switch",
-      1, 1, ofctl_dump_meters },
+      1, 1, ofctl_dump_meters, OVS_RO },
     { "meter-stats", "switch [meter]",
-      1, 2, ofctl_meter_stats },
+      1, 2, ofctl_meter_stats, OVS_RO },
     { "meter-features", "switch",
-      1, 1, ofctl_meter_features },
+      1, 1, ofctl_meter_features, OVS_RO },
     { "packet-out", "switch in_port actions packet...",
-      4, INT_MAX, ofctl_packet_out },
+      4, INT_MAX, ofctl_packet_out, OVS_RW },
     { "dump-ports", "switch [port]",
-      1, 2, ofctl_dump_ports },
+      1, 2, ofctl_dump_ports, OVS_RO },
     { "dump-ports-desc", "switch [port]",
-      1, 2, ofctl_dump_ports_desc },
+      1, 2, ofctl_dump_ports_desc, OVS_RO },
     { "mod-port", "switch iface act",
-      3, 3, ofctl_mod_port },
+      3, 3, ofctl_mod_port, OVS_RW },
     { "mod-table", "switch mod",
-      3, 3, ofctl_mod_table },
+      3, 3, ofctl_mod_table, OVS_RW },
     { "get-frags", "switch",
-      1, 1, ofctl_get_frags },
+      1, 1, ofctl_get_frags, OVS_RO },
     { "set-frags", "switch frag_mode",
-      2, 2, ofctl_set_frags },
+      2, 2, ofctl_set_frags, OVS_RW },
     { "probe", "target",
-      1, 1, ofctl_probe },
+      1, 1, ofctl_probe, OVS_RO },
     { "ping", "target [n]",
-      1, 2, ofctl_ping },
+      1, 2, ofctl_ping, OVS_RO },
     { "benchmark", "target n count",
-      3, 3, ofctl_benchmark },
+      3, 3, ofctl_benchmark, OVS_RO },
 
     { "dump-ipfix-bridge", "switch",
-      1, 1, ofctl_dump_ipfix_bridge},
+      1, 1, ofctl_dump_ipfix_bridge, OVS_RO },
     { "dump-ipfix-flow", "switch",
-      1, 1, ofctl_dump_ipfix_flow},
+      1, 1, ofctl_dump_ipfix_flow, OVS_RO },
 
     { "ofp-parse", "file",
-      1, 1, ofctl_ofp_parse },
+      1, 1, ofctl_ofp_parse, OVS_RW },
     { "ofp-parse-pcap", "pcap",
-      1, INT_MAX, ofctl_ofp_parse_pcap },
+      1, INT_MAX, ofctl_ofp_parse_pcap, OVS_RW },
 
     { "add-group", "switch group",
-      1, 2, ofctl_add_group },
+      1, 2, ofctl_add_group, OVS_RW },
     { "add-groups", "switch file",
-      1, 2, ofctl_add_groups },
+      1, 2, ofctl_add_groups, OVS_RW },
     { "mod-group", "switch group",
-      1, 2, ofctl_mod_group },
+      1, 2, ofctl_mod_group, OVS_RW },
     { "del-groups", "switch [group]",
-      1, 2, ofctl_del_groups },
+      1, 2, ofctl_del_groups, OVS_RW },
     { "insert-buckets", "switch [group]",
-      1, 2, ofctl_insert_bucket },
+      1, 2, ofctl_insert_bucket, OVS_RW },
     { "remove-buckets", "switch [group]",
-      1, 2, ofctl_remove_bucket },
+      1, 2, ofctl_remove_bucket, OVS_RW },
     { "dump-groups", "switch [group]",
-      1, 2, ofctl_dump_group_desc },
+      1, 2, ofctl_dump_group_desc, OVS_RO },
     { "dump-group-stats", "switch [group]",
-      1, 2, ofctl_dump_group_stats },
+      1, 2, ofctl_dump_group_stats, OVS_RO },
     { "dump-group-features", "switch",
-      1, 1, ofctl_dump_group_features },
+      1, 1, ofctl_dump_group_features, OVS_RO },
 
     { "bundle", "switch file",
-      2, 2, ofctl_bundle },
+      2, 2, ofctl_bundle, OVS_RW },
 
     { "add-tlv-map", "switch map",
-      2, 2, ofctl_add_tlv_map },
+      2, 2, ofctl_add_tlv_map, OVS_RO },
     { "del-tlv-map", "switch [map]",
-      1, 2, ofctl_del_tlv_map },
+      1, 2, ofctl_del_tlv_map, OVS_RO },
     { "dump-tlv-map", "switch",
-      1, 1, ofctl_dump_tlv_map },
-    { "help", NULL, 0, INT_MAX, ofctl_help },
-    { "list-commands", NULL, 0, INT_MAX, ofctl_list_commands },
+      1, 1, ofctl_dump_tlv_map, OVS_RO },
+    { "help", NULL, 0, INT_MAX, ofctl_help, OVS_RO },
+    { "list-commands", NULL, 0, INT_MAX, ofctl_list_commands, OVS_RO },
 
     /* Undocumented commands for testing. */
-    { "parse-flow", NULL, 1, 1, ofctl_parse_flow },
-    { "parse-flows", NULL, 1, 1, ofctl_parse_flows },
-    { "parse-nx-match", NULL, 0, 0, ofctl_parse_nxm },
-    { "parse-nxm", NULL, 0, 0, ofctl_parse_nxm },
-    { "parse-oxm", NULL, 1, 1, ofctl_parse_oxm },
-    { "parse-actions", NULL, 1, 1, ofctl_parse_actions },
-    { "parse-instructions", NULL, 1, 1, ofctl_parse_instructions },
-    { "parse-ofp10-match", NULL, 0, 0, ofctl_parse_ofp10_match },
-    { "parse-ofp11-match", NULL, 0, 0, ofctl_parse_ofp11_match },
-    { "parse-pcap", NULL, 1, INT_MAX, ofctl_parse_pcap },
-    { "check-vlan", NULL, 2, 2, ofctl_check_vlan },
-    { "print-error", NULL, 1, 1, ofctl_print_error },
-    { "encode-error-reply", NULL, 2, 2, ofctl_encode_error_reply },
-    { "ofp-print", NULL, 1, 2, ofctl_ofp_print },
-    { "encode-hello", NULL, 1, 1, ofctl_encode_hello },
-    { "parse-key-value", NULL, 1, INT_MAX, ofctl_parse_key_value },
-
-    { NULL, NULL, 0, 0, NULL },
+    { "parse-flow", NULL, 1, 1, ofctl_parse_flow, OVS_RW },
+    { "parse-flows", NULL, 1, 1, ofctl_parse_flows, OVS_RW },
+    { "parse-nx-match", NULL, 0, 0, ofctl_parse_nxm, OVS_RW },
+    { "parse-nxm", NULL, 0, 0, ofctl_parse_nxm, OVS_RW },
+    { "parse-oxm", NULL, 1, 1, ofctl_parse_oxm, OVS_RW },
+    { "parse-actions", NULL, 1, 1, ofctl_parse_actions, OVS_RW },
+    { "parse-instructions", NULL, 1, 1, ofctl_parse_instructions, OVS_RW },
+    { "parse-ofp10-match", NULL, 0, 0, ofctl_parse_ofp10_match, OVS_RW },
+    { "parse-ofp11-match", NULL, 0, 0, ofctl_parse_ofp11_match, OVS_RW },
+    { "parse-pcap", NULL, 1, INT_MAX, ofctl_parse_pcap, OVS_RW },
+    { "check-vlan", NULL, 2, 2, ofctl_check_vlan, OVS_RW },
+    { "print-error", NULL, 1, 1, ofctl_print_error, OVS_RW },
+    { "encode-error-reply", NULL, 2, 2, ofctl_encode_error_reply, OVS_RW },
+    { "ofp-print", NULL, 1, 2, ofctl_ofp_print, OVS_RW },
+    { "encode-hello", NULL, 1, 1, ofctl_encode_hello, OVS_RW },
+    { "parse-key-value", NULL, 1, INT_MAX, ofctl_parse_key_value, OVS_RW },
+
+    { NULL, NULL, 0, 0, NULL, OVS_RO },
 };
 
 static const struct ovs_cmdl_command *get_all_commands(void)
-- 
2.7.4




More information about the dev mailing list