[ovs-dev] [PATCH] ovn: add ovn-nbctl

Russell Bryant rbryant at redhat.com
Sun Mar 22 02:43:52 UTC 2015


ovn-nbctl is intended to be a utility for manually interacting with
the OVN northbound db.  A real consumer of OVN, such as OpenStack,
should be using ovsdb directly.  However, a utility like this is
useful during development and testing of both ovn itself as well as
the OpenStack Neutron plugin.  It could also be used when debugging
the state of an ovn deployment.

The commands related to logical switches and ports have been
implemented.  You can add, delete, list, and get/set external:ids.
On ports you can also get/set MAC addresses.

Signed-off-by: Russell Bryant <rbryant at redhat.com>
---


v1
 - first cut of lswitch-* commands
v2
 - fix handling of multiple switches with the same name
v3
 - add -d/--database option
 - add lport-* commands


 ovn/.gitignore      |   2 +
 ovn/automake.mk     |   8 +-
 ovn/ovn-nbctl.8.xml |  33 +++
 ovn/ovn-nbctl.c     | 603 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 644 insertions(+), 2 deletions(-)
 create mode 100644 ovn/ovn-nbctl.8.xml
 create mode 100644 ovn/ovn-nbctl.c

diff --git a/ovn/.gitignore b/ovn/.gitignore
index 011bb1c..f320a6b 100644
--- a/ovn/.gitignore
+++ b/ovn/.gitignore
@@ -12,3 +12,5 @@
 /ovn-nb-idl.c
 /ovn-nb-idl.h
 /ovn-nb-idl.ovsidl
+/ovn-nbctl
+/ovn-nbctl.8
diff --git a/ovn/automake.mk b/ovn/automake.mk
index cc1f8ae..37b0ca6 100644
--- a/ovn/automake.mk
+++ b/ovn/automake.mk
@@ -66,8 +66,8 @@ ovn/ovn-nb.5: \
 		$(srcdir)/ovn/ovn-nb.xml > $@.tmp && \
 	mv $@.tmp $@
 
-man_MANS += ovn/ovn-controller.8 ovn/ovn-architecture.7
-EXTRA_DIST += ovn/ovn-controller.8.in ovn/ovn-architecture.7.xml
+man_MANS += ovn/ovn-controller.8 ovn/ovn-architecture.7 ovn/ovn-nbctl.8
+EXTRA_DIST += ovn/ovn-controller.8.in ovn/ovn-architecture.7.xml ovn/ovn-nbctl.8.xml
 
 SUFFIXES += .xml
 %: %.xml
@@ -115,3 +115,7 @@ ovn_libovn_la_SOURCES = \
 	ovn/ovn-idl.h \
 	ovn/ovn-nb-idl.c \
 	ovn/ovn-nb-idl.h
+
+bin_PROGRAMS += ovn/ovn-nbctl
+ovn_ovn_nbctl_SOURCES = ovn/ovn-nbctl.c
+ovn_ovn_nbctl_LDADD = ovn/libovn.la ovsdb/libovsdb.la lib/libopenvswitch.la
diff --git a/ovn/ovn-nbctl.8.xml b/ovn/ovn-nbctl.8.xml
new file mode 100644
index 0000000..2a87eec
--- /dev/null
+++ b/ovn/ovn-nbctl.8.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manpage program="ovn-nbctl" section="8" title="ovn-nbctl">
+    <h1>Name</h1>
+    <p>ovn-nbctl -- Open Virtual Network northbound db management utility</p>
+
+    <h1>Synopsys</h1>
+    <p>ovn-nbctl [OPTIONS] COMMAND [ARG...]</p>
+
+    <h1>Description</h1>
+    <p>This utility can be used to manage the OVN northbound database.</p>
+
+    <h1>Logical Switch Commands</h1>
+    <p>lswitch-add [name]</p>
+    <p>lswitch-del &lt;lswitch&gt;</p>
+    <p>lswitch-list</p>
+    <p>lswitch-set-external-id &lt;lswitch&gt; &lt;key&gt; [value]</p>
+    <p>lswitch-get-external-id &lt;lswitch&gt; [key]</p>
+
+    <h1>Logical Port Commands</h1>
+    <p>lport-add &lt;name&gt; &lt;lswitch&gt;</p>
+    <p>lport-del &lt;lport&gt;</p>
+    <p>lport-list &lt;lswitch&gt;</p>
+    <p>lport-set-external-id &lt;lport&gt; &lt;key&gt; [value]</p>
+    <p>lport-get-external-id &lt;lport&gt; [key]</p>
+    <p>lport-set-macs &lt;lport&gt; [MAC] [MAC] [...]</p>
+    <p>lport-get-macs &lt;lport&gt;</p>
+
+    <h1>Options</h1>
+    <p>-d DATABASE | --database DATABASE</p>
+    <p>-h | --help</p>
+    <p>-o | --options</p>
+    <p>-V | --version</p>
+</manpage>
diff --git a/ovn/ovn-nbctl.c b/ovn/ovn-nbctl.c
new file mode 100644
index 0000000..9952b46
--- /dev/null
+++ b/ovn/ovn-nbctl.c
@@ -0,0 +1,603 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <getopt.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "command-line.h"
+#include "dirs.h"
+#include "fatal-signal.h"
+#include "ovn/ovn-nb-idl.h"
+#include "poll-loop.h"
+#include "util.h"
+#include "openvswitch/vlog.h"
+
+struct nbctl_context {
+    struct ovsdb_idl *idl;
+    struct ovsdb_idl_txn *txn;
+};
+
+static const char *db;
+
+static const char *default_db(void);
+
+static void
+usage(void)
+{
+    printf("\
+%s: OVN northbound DB management utility\n\
+usage: %s [OPTIONS] COMMAND [ARG...]\n\
+\n\
+Logical Switch Commands:\n\
+  lswitch-add [name]        Create a logical switch\n\
+  lswitch-del <lswitch>     Delete a logical switch\n\
+  lswitch-list              List configured logical switches\n\
+  lswitch-set-external-id <lswitch> <key> [value]\n\
+                            Set or delete an external:id on a logical switch\n\
+  lswitch-get-external-id <lswitch> [key]\n\
+                            List one or all external:ids set on a switch\n\
+\n\
+Logical Port Commands:\n\
+  lport-add <name> <lswitch> Create a logical port on a logical switch\n\
+  lport-del <lport>         Delete a logical port (by name or UUID)\n\
+  lport-list <lswitch>      List ports on a logical switch\n\
+  lport-set-external-id <lport> <key> [value]\n\
+                            Set or delete an external:id on a logical port\n\
+  lport-get-external-id <lport> [key]\n\
+                            List one or all external:ids set on a port\n\
+  lport-set-macs <lport> [MAC] [MAC] [...]\n\
+                            Set MAC addresses for the logical port. Specify\n\
+                            more than one using additional arguments.\n\
+  lport-get-macs <lport>    Get a list of MAC addresses on the port.\n\
+\n\
+Options:\n\
+  --db=DATABASE             connect to DATABASE\n\
+                            (default: %s)\n\
+  -h, --help                display this help message\n\
+  -o, --options             list available options\n\
+  -V, --version             display version information\n\
+", program_name, program_name, default_db());
+}
+
+static const struct nbrec_logical_switch *
+lswitch_by_name_or_uuid(struct nbctl_context *nb_ctx, const char *id)
+{
+    const struct nbrec_logical_switch *lswitch = NULL;
+    int is_uuid = 0;
+    int duplicate = 0;
+    struct uuid lswitch_uuid;
+
+    if (uuid_from_string(&lswitch_uuid, id)) {
+        is_uuid = 1;
+        lswitch = nbrec_logical_switch_get_for_uuid(nb_ctx->idl, &lswitch_uuid);
+    } else {
+        const struct nbrec_logical_switch *iter;
+
+        NBREC_LOGICAL_SWITCH_FOR_EACH(iter, nb_ctx->idl) {
+            if (strcmp(iter->name, id)) {
+                continue;
+            }
+            if (lswitch) {
+                printf("There is more than one logical switch named '%s'. "
+                        "Use a UUID.\n", id);
+                lswitch = NULL;
+                duplicate = 1;
+                break;
+            }
+            lswitch = iter;
+        }
+    }
+
+    if (!lswitch && !duplicate) {
+        printf("lswitch not found for %s: '%s'\n",
+                is_uuid ? "UUID" : "name", id);
+    }
+
+    return lswitch;
+}
+
+static void
+do_lswitch_add(struct ovs_cmdl_context *ctx)
+{
+    struct nbctl_context *nb_ctx = ctx->pvt;
+    struct nbrec_logical_switch *lswitch;
+
+    lswitch = nbrec_logical_switch_insert(nb_ctx->txn);
+    if (ctx->argc == 2) {
+        nbrec_logical_switch_set_name(lswitch, ctx->argv[1]);
+    }
+}
+
+static void
+do_lswitch_del(struct ovs_cmdl_context *ctx)
+{
+    struct nbctl_context *nb_ctx = ctx->pvt;
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_switch *lswitch;
+
+    lswitch = lswitch_by_name_or_uuid(nb_ctx, id);
+    if (!lswitch) {
+        return;
+    }
+
+    nbrec_logical_switch_delete(lswitch);
+}
+
+static void
+do_lswitch_list(struct ovs_cmdl_context *ctx)
+{
+    struct nbctl_context *nb_ctx = ctx->pvt;
+    const struct nbrec_logical_switch *lswitch;
+
+    NBREC_LOGICAL_SWITCH_FOR_EACH(lswitch, nb_ctx->idl) {
+        printf(UUID_FMT " (%s)\n", UUID_ARGS(&lswitch->header_.uuid), lswitch->name);
+    }
+}
+
+static void
+do_lswitch_set_external_id(struct ovs_cmdl_context *ctx)
+{
+    struct nbctl_context *nb_ctx = ctx->pvt;
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_switch *lswitch;
+    struct smap new_external_ids;
+
+    lswitch = lswitch_by_name_or_uuid(nb_ctx, id);
+    if (!lswitch) {
+        return;
+    }
+
+    smap_init(&new_external_ids);
+    smap_clone(&new_external_ids, &lswitch->external_ids);
+    if (ctx->argc == 4) {
+        smap_replace(&new_external_ids, ctx->argv[2], ctx->argv[3]);
+    } else {
+        smap_remove(&new_external_ids, ctx->argv[2]);
+    }
+    nbrec_logical_switch_set_external_ids(lswitch, &new_external_ids);
+    smap_destroy(&new_external_ids);
+}
+
+static void
+do_lswitch_get_external_id(struct ovs_cmdl_context *ctx)
+{
+    struct nbctl_context *nb_ctx = ctx->pvt;
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_switch *lswitch;
+
+    lswitch = lswitch_by_name_or_uuid(nb_ctx, id);
+    if (!lswitch) {
+        return;
+    }
+
+    if (ctx->argc == 3) {
+        const char *key = ctx->argv[2];
+        const char *value;
+
+        /* List one external ID */
+
+        value = smap_get(&lswitch->external_ids, key);
+        if (value && *value) {
+            printf("%s\n", value);
+        } else {
+            printf("external-id '%s' is not set.\n", key);
+        }
+    } else {
+        struct smap_node *node;
+
+        /* List all external IDs */
+
+        SMAP_FOR_EACH(node, &lswitch->external_ids) {
+            printf("%s=%s\n", node->key, node->value);
+        }
+    }
+}
+
+static const struct nbrec_logical_port *
+lport_by_name_or_uuid(struct nbctl_context *nb_ctx, const char *id)
+{
+    const struct nbrec_logical_port *lport = NULL;
+    int is_uuid = 0;
+    struct uuid lport_uuid;
+
+    if (uuid_from_string(&lport_uuid, id)) {
+        is_uuid = 1;
+        lport = nbrec_logical_port_get_for_uuid(nb_ctx->idl, &lport_uuid);
+    } else {
+        NBREC_LOGICAL_PORT_FOR_EACH(lport, nb_ctx->idl) {
+            if (!strcmp(lport->name, id)) {
+                break;
+            }
+        }
+    }
+
+    if (!lport) {
+        printf("lport not found for %s: '%s'\n",
+                is_uuid ? "UUID" : "name", id);
+    }
+
+    return lport;
+}
+
+static void
+do_lport_add(struct ovs_cmdl_context *ctx)
+{
+    struct nbctl_context *nb_ctx = ctx->pvt;
+    struct nbrec_logical_port *lport;
+    const struct nbrec_logical_switch *lswitch;
+
+    lswitch = lswitch_by_name_or_uuid(nb_ctx, ctx->argv[2]);
+    if (!lswitch) {
+        return;
+    }
+
+    lport = nbrec_logical_port_insert(nb_ctx->txn);
+    nbrec_logical_port_set_name(lport, ctx->argv[1]);
+    nbrec_logical_port_set_lswitch(lport, lswitch);
+}
+
+static void
+do_lport_del(struct ovs_cmdl_context *ctx)
+{
+    struct nbctl_context *nb_ctx = ctx->pvt;
+    const struct nbrec_logical_port *lport;
+
+    lport = lport_by_name_or_uuid(nb_ctx, ctx->argv[1]);
+    if (!lport) {
+        return;
+    }
+
+    nbrec_logical_port_delete(lport);
+}
+
+static bool
+is_lswitch(const struct nbrec_logical_switch *lswitch,
+        struct uuid *lswitch_uuid, const char *name)
+{
+    if (lswitch_uuid) {
+        return uuid_equals(lswitch_uuid, &lswitch->header_.uuid);
+    } else {
+        return !strcmp(lswitch->name, name);
+    }
+}
+
+
+static void
+do_lport_list(struct ovs_cmdl_context *ctx)
+{
+    struct nbctl_context *nb_ctx = ctx->pvt;
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_port *lport;
+    int is_uuid = 0;
+    struct uuid lswitch_uuid;
+
+    if (uuid_from_string(&lswitch_uuid, id)) {
+        is_uuid = 1;
+    }
+
+    NBREC_LOGICAL_PORT_FOR_EACH(lport, nb_ctx->idl) {
+        bool match;
+        if (is_uuid) {
+            match = is_lswitch(lport->lswitch, &lswitch_uuid, NULL);
+        } else {
+            match = is_lswitch(lport->lswitch, NULL, id);
+        }
+        if (!match) {
+            continue;
+        }
+        printf(UUID_FMT " (%s)\n", UUID_ARGS(&lport->header_.uuid), lport->name);
+    }
+}
+
+static void
+do_lport_set_external_id(struct ovs_cmdl_context *ctx)
+{
+    struct nbctl_context *nb_ctx = ctx->pvt;
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_port *lport;
+    struct smap new_external_ids;
+
+    lport = lport_by_name_or_uuid(nb_ctx, id);
+    if (!lport) {
+        return;
+    }
+
+    smap_init(&new_external_ids);
+    smap_clone(&new_external_ids, &lport->external_ids);
+    if (ctx->argc == 4) {
+        smap_replace(&new_external_ids, ctx->argv[2], ctx->argv[3]);
+    } else {
+        smap_remove(&new_external_ids, ctx->argv[2]);
+    }
+    nbrec_logical_port_set_external_ids(lport, &new_external_ids);
+    smap_destroy(&new_external_ids);
+}
+
+static void
+do_lport_get_external_id(struct ovs_cmdl_context *ctx)
+{
+    struct nbctl_context *nb_ctx = ctx->pvt;
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_port *lport;
+
+    lport = lport_by_name_or_uuid(nb_ctx, id);
+    if (!lport) {
+        return;
+    }
+
+    if (ctx->argc == 3) {
+        const char *key = ctx->argv[2];
+        const char *value;
+
+        /* List one external ID */
+
+        value = smap_get(&lport->external_ids, key);
+        if (value && *value) {
+            printf("%s\n", value);
+        } else {
+            printf("external-id '%s' is not set.\n", key);
+        }
+    } else {
+        struct smap_node *node;
+
+        /* List all external IDs */
+
+        SMAP_FOR_EACH(node, &lport->external_ids) {
+            printf("%s=%s\n", node->key, node->value);
+        }
+    }
+}
+
+static void
+do_lport_set_macs(struct ovs_cmdl_context *ctx)
+{
+    struct nbctl_context *nb_ctx = ctx->pvt;
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_port *lport;
+
+    lport = lport_by_name_or_uuid(nb_ctx, id);
+    if (!lport) {
+        return;
+    }
+
+    nbrec_logical_port_set_macs(lport,
+            (const char **) ctx->argv + 2, ctx->argc - 2);
+}
+
+static void
+do_lport_get_macs(struct ovs_cmdl_context *ctx)
+{
+    struct nbctl_context *nb_ctx = ctx->pvt;
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_port *lport;
+    size_t i;
+
+    lport = lport_by_name_or_uuid(nb_ctx, id);
+    if (!lport) {
+        return;
+    }
+
+    for (i = 0; i < lport->n_macs; i++) {
+        printf("%s\n", lport->macs[i]);
+    }
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    static const struct option long_options[] = {
+        {"database", required_argument, NULL, 'd'},
+        {"help", no_argument, NULL, 'h'},
+        {"options", no_argument, NULL, 'o'},
+        {"version", no_argument, NULL, 'V'},
+        {NULL, 0, NULL, 0},
+    };
+    char *short_options = ovs_cmdl_long_options_to_short_options(long_options);
+
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 'd':
+            db = optarg;
+            break;
+
+        case 'h':
+            usage();
+            exit(EXIT_SUCCESS);
+
+        case 'o':
+            ovs_cmdl_print_options(long_options);
+            exit(EXIT_SUCCESS);
+
+        case 'V':
+            ovs_print_version(0, 0);
+            exit(EXIT_SUCCESS);
+
+        default:
+            break;
+        }
+    }
+
+    if (!db) {
+        db = default_db();
+    }
+
+    free(short_options);
+}
+
+static const struct ovs_cmdl_command all_commands[] = {
+    {
+        .name = "lswitch-add",
+        .usage = "[lswitch]",
+        .min_args = 0,
+        .max_args = 1,
+        .handler = do_lswitch_add,
+    },
+    {
+        .name = "lswitch-del",
+        .usage = "<lswitch>",
+        .min_args = 1,
+        .max_args = 1,
+        .handler = do_lswitch_del,
+    },
+    {
+        .name = "lswitch-list",
+        .usage = "",
+        .min_args = 0,
+        .max_args = 0,
+        .handler = do_lswitch_list,
+    },
+    {
+        .name = "lswitch-set-external-id",
+        .usage = "<lswitch> <key> [value]",
+        .min_args = 2,
+        .max_args = 3,
+        .handler = do_lswitch_set_external_id,
+    },
+    {
+        .name = "lswitch-get-external-id",
+        .usage = "<lswitch> [key]",
+        .min_args = 1,
+        .max_args = 2,
+        .handler = do_lswitch_get_external_id,
+    },
+    {
+        .name = "lport-add",
+        .usage = "<name> <lswitch>",
+        .min_args = 2,
+        .max_args = 2,
+        .handler = do_lport_add,
+    },
+    {
+        .name = "lport-del",
+        .usage = "<lport>",
+        .min_args = 1,
+        .max_args = 1,
+        .handler = do_lport_del,
+    },
+    {
+        .name = "lport-list",
+        .usage = "<lswitch>",
+        .min_args = 1,
+        .max_args = 1,
+        .handler = do_lport_list,
+    },
+    {
+        .name = "lport-set-external-id",
+        .usage = "<lport> <key> [value]",
+        .min_args = 2,
+        .max_args = 3,
+        .handler = do_lport_set_external_id,
+    },
+    {
+        .name = "lport-get-external-id",
+        .usage = "<lport> [key]",
+        .min_args = 1,
+        .max_args = 2,
+        .handler = do_lport_get_external_id,
+    },
+    {
+        .name = "lport-set-macs",
+        .usage = "<lport> [MAC] [MAC] [...]",
+        .min_args = 1,
+        /* Accept however many arguments the system will allow. */
+        .max_args = INT_MAX,
+        .handler = do_lport_set_macs,
+    },
+    {
+        .name = "lport-get-macs",
+        .usage = "<lport>",
+        .min_args = 1,
+        .max_args = 1,
+        .handler = do_lport_get_macs,
+    },
+
+    {
+        /* sentinel */
+        .name = NULL,
+    },
+};
+
+static const struct ovs_cmdl_command *
+get_all_commands(void)
+{
+    return all_commands;
+}
+
+static const char *
+default_db(void)
+{
+    static char *db;
+    if (!db) {
+        db = xasprintf("unix:%s/db.sock", ovs_rundir());
+    }
+    return db;
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct ovs_cmdl_context ctx;
+    struct nbctl_context nb_ctx = { .idl = NULL, };
+    enum ovsdb_idl_txn_status txn_status;
+    unsigned int seqno;
+
+    fatal_ignore_sigpipe();
+    set_program_name(argv[0]);
+    parse_options(argc, argv);
+    nbrec_init();
+
+    nb_ctx.idl = ovsdb_idl_create(db, &nbrec_idl_class, true, false);
+    ctx.pvt = &nb_ctx;
+    ctx.argc = argc - optind;
+    ctx.argv = argv + optind;
+
+    seqno = ovsdb_idl_get_seqno(nb_ctx.idl);
+    for (;;) {
+        ovsdb_idl_run(nb_ctx.idl);
+
+        if (!ovsdb_idl_is_alive(nb_ctx.idl)) {
+            int retval = ovsdb_idl_get_last_error(nb_ctx.idl);
+            printf("%s: database connection failed (%s)",
+                   db, ovs_retval_to_string(retval));
+        }
+
+        if (seqno != ovsdb_idl_get_seqno(nb_ctx.idl)) {
+            nb_ctx.txn = ovsdb_idl_txn_create(nb_ctx.idl);
+            ovs_cmdl_run_command(&ctx, get_all_commands());
+            txn_status = ovsdb_idl_txn_commit_block(nb_ctx.txn);
+            if (txn_status == TXN_TRY_AGAIN) {
+                continue;
+            } else {
+                break;
+            }
+        }
+
+        if (seqno == ovsdb_idl_get_seqno(nb_ctx.idl)) {
+            ovsdb_idl_wait(nb_ctx.idl);
+            poll_block();
+        }
+    }
+
+    exit(0);
+}
-- 
2.1.0




More information about the dev mailing list