ovn-sbctl: Add ovn-sbctl.
authorAlex Wang <alexw@nicira.com>
Tue, 9 Jun 2015 17:13:34 +0000 (10:13 -0700)
committerAlex Wang <alexw@nicira.com>
Sat, 8 Aug 2015 16:49:35 +0000 (09:49 -0700)
This commit adds ovn-sbctl to ovn family by using the db-ctl-base
library.

Signed-off-by: Alex Wang <alexw@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
Acked-by: Russell Bryant <rbryant@redhat.com>
manpages.mk
ovn/utilities/.gitignore
ovn/utilities/automake.mk
ovn/utilities/ovn-sbctl.8.in [new file with mode: 0644]
ovn/utilities/ovn-sbctl.c [new file with mode: 0644]
tests/automake.mk
tests/ovn-sbctl.at [new file with mode: 0644]
tests/testsuite.at

index 3cec260..6e2853b 100644 (file)
@@ -1,5 +1,17 @@
 # Generated automatically -- do not modify!    -*- buffer-read-only: t -*-
 
+ovn/utilities/ovn-sbctl.8: \
+       ovn/utilities/ovn-sbctl.8.in \
+       lib/db-ctl-base.man \
+       lib/table.man \
+       ovsdb/remote-active.man \
+       ovsdb/remote-passive.man
+ovn/utilities/ovn-sbctl.8.in:
+lib/db-ctl-base.man:
+lib/table.man:
+ovsdb/remote-active.man:
+ovsdb/remote-passive.man:
+
 ovsdb/ovsdb-client.1: \
        ovsdb/ovsdb-client.1.in \
        lib/common-syn.man \
index 8769651..5832b6c 100644 (file)
@@ -1,3 +1,5 @@
 /ovn-ctl.8
 /ovn-nbctl
 /ovn-nbctl.8
+/ovn-sbctl
+/ovn-sbctl.8
index 145ee44..b247a54 100644 (file)
@@ -3,7 +3,10 @@ scripts_SCRIPTS += \
 
 man_MANS += \
     ovn/utilities/ovn-ctl.8 \
-    ovn/utilities/ovn-nbctl.8
+    ovn/utilities/ovn-nbctl.8 \
+    ovn/utilities/ovn-sbctl.8
+
+MAN_ROOTS += ovn/utilities/ovn-sbctl.8.in
 
 EXTRA_DIST += \
     ovn/utilities/ovn-ctl \
@@ -12,9 +15,15 @@ EXTRA_DIST += \
 
 DISTCLEANFILES += \
     ovn/utilities/ovn-ctl.8 \
-    ovn/utilities/ovn-nbctl.8
+    ovn/utilities/ovn-nbctl.8 \
+    ovn/utilities/ovn-sbctl.8
 
 # ovn-nbctl
 bin_PROGRAMS += ovn/utilities/ovn-nbctl
 ovn_utilities_ovn_nbctl_SOURCES = ovn/utilities/ovn-nbctl.c
 ovn_utilities_ovn_nbctl_LDADD = ovn/lib/libovn.la ovsdb/libovsdb.la lib/libopenvswitch.la
+
+# ovn-sbctl
+bin_PROGRAMS += ovn/utilities/ovn-sbctl
+ovn_utilities_ovn_sbctl_SOURCES = ovn/utilities/ovn-sbctl.c
+ovn_utilities_ovn_sbctl_LDADD = ovn/lib/libovn.la ovsdb/libovsdb.la lib/libopenvswitch.la
diff --git a/ovn/utilities/ovn-sbctl.8.in b/ovn/utilities/ovn-sbctl.8.in
new file mode 100644 (file)
index 0000000..b5c796e
--- /dev/null
@@ -0,0 +1,160 @@
+.\" -*- nroff -*-
+.de IQ
+.  br
+.  ns
+.  IP "\\$1"
+..
+.de ST
+.  PP
+.  RS -0.15in
+.  I "\\$1"
+.  RE
+..
+.TH ovn\-sbctl 8 "@VERSION@" "Open vSwitch" "Open vSwitch Manual"
+.\" This program's name:
+.ds PN ovn\-sbctl
+.
+.SH NAME
+ovn\-sbctl \- utility for querying and configuring \fBOVN_Southbound\fR database
+.
+.SH SYNOPSIS
+\fBovn\-sbctl\fR [\fIoptions\fR] \fB\-\-\fR [\fIoptions\fR] \fIcommand
+\fR[\fIargs\fR] [\fB\-\-\fR [\fIoptions\fR] \fIcommand \fR[\fIargs\fR]]...
+.
+.SH DESCRIPTION
+The command should only be used for advanced debugging and troubleshooting
+of the \fBOVN_Southbound\fR database; and should never be used in normal
+operation.
+.PP
+The \fBovn\-sbctl\fR program configures the \fBOVN_Southbound\fR database
+by providing a high\-level interface to its configuration database.  See
+\fBovn\-sb\fR(5) for comprehensive documentation of the database schema.
+.PP
+\fBovn\-sbctl\fR connects to an \fBovsdb\-server\fR process that
+maintains an OVN_Southbound configuration database.  Using this
+connection, it queries and possibly applies changes to the database,
+depending on the supplied commands.
+.PP
+\fBovn\-sbctl\fR can perform any number of commands in a single run,
+implemented as a single atomic transaction against the database.
+.PP
+The \fBovn\-sbctl\fR command line begins with global options (see
+\fBOPTIONS\fR below for details).  The global options are followed by
+one or more commands.  Each command should begin with \fB\-\-\fR by
+itself as a command-line argument, to separate it from the following
+commands.  (The \fB\-\-\fR before the first command is optional.)  The
+command
+itself starts with command-specific options, if any, followed by the
+command name and any arguments.
+.
+.SH OPTIONS
+.
+The following options affect the behavior of \fBovn\-sbctl\fR as a
+whole.  Some individual commands also accept their own options, which
+are given just before the command name.  If the first command on the
+command line has options, then those options must be separated from
+the global options by \fB\-\-\fR.
+.
+.IP "\fB\-\-db=\fIserver\fR"
+Sets \fIserver\fR as the database server that \fBovn\-sbctl\fR
+contacts to query or modify configuration.  The default is
+\fBunix:@RUNDIR@/db.sock\fR.  \fIserver\fR must take one of the
+following forms:
+.RS
+.so ovsdb/remote-active.man
+.so ovsdb/remote-passive.man
+.RE
+.
+.IP "\fB\-\-no\-syslog\fR"
+By default, \fBovn\-sbctl\fR logs its arguments and the details of any
+changes that it makes to the system log.  This option disables this
+logging.
+.IP
+This option is equivalent to \fB\-\-verbose=sbctl:syslog:warn\fR.
+.
+.IP "\fB\-\-oneline\fR"
+Modifies the output format so that the output for each command is printed
+on a single line.  New-line characters that would otherwise separate
+lines are printed as \fB\\n\fR, and any instances of \fB\\\fR that
+would otherwise appear in the output are doubled.
+Prints a blank line for each command that has no output.
+This option does not affect the formatting of output from the
+\fBlist\fR or \fBfind\fR commands; see \fBTable Formatting Options\fR
+below.
+.
+.IP "\fB\-\-dry\-run\fR"
+Prevents \fBovn\-sbctl\fR from actually modifying the database.
+.
+.IP "\fB\-t \fIsecs\fR"
+.IQ "\fB\-\-timeout=\fIsecs\fR"
+By default, or with a \fIsecs\fR of \fB0\fR, \fBovn\-sbctl\fR waits
+forever for a response from the database.  This option limits runtime
+to approximately \fIsecs\fR seconds.  If the timeout expires,
+\fBovn\-sbctl\fR will exit with a \fBSIGALRM\fR signal.  (A timeout
+would normally happen only if the database cannot be contacted, or if
+the system is overloaded.)
+.
+.SS "Table Formatting Options"
+These options control the format of output from the \fBlist\fR and
+\fBfind\fR commands.
+.so lib/table.man
+.
+.SH COMMANDS
+The commands implemented by \fBovn\-sbctl\fR are described in the
+sections below.
+.SS "OVN_Southbound Commands"
+These commands work with an \fBOVN_Southbound\fR database as a whole.
+.
+.IP "\fBshow\fR"
+Prints a brief overview of the database contents.
+.
+.SS "Chassis Commands"
+These commands manipulate \fBOVN_Southbound\fR chassis.
+.
+.IP "[\fB\-\-may\-exist\fR] \fBchassis\-add \fIchassis\fR \fIencap-type\fR \fIencap-ip\fR"
+Creates a new chassis named \fIchassis\fR.  The chassis will have
+one encap entry with \fIencap-type\fR as tunnel type and \fIencap-ip\fR
+as destination ip.
+.IP
+Without \fB\-\-may\-exist\fR, attempting to create a chassis that
+exists is an error.  With \fB\-\-may\-exist\fR, this command does
+nothing if \fIchassis\fR already exists as a real bridge.
+.
+.IP "[\fB\-\-if\-exists\fR] \fBchassis\-del \fIchassis\fR"
+Deletes \fIchassis\fR and its \fIencaps\fR and \fIgateway_ports\fR.
+.IP
+Without \fB\-\-if\-exists\fR, attempting to delete a chassis that does
+not exist is an error.  With \fB\-\-if\-exists\fR, attempting to
+delete a chassis that does not exist has no effect.
+.
+.SS "Port binding Commands"
+.
+These commands manipulate \fBOVN_Southbound\fR port bindings.
+.
+.IP "[\fB\-\-may\-exist\fR] \fBlport\-bind \fIlogical\-port\fR \fIchassis\fR"
+Binds the logical port named \fIlogical\-port\fR to \fIchassis\fR.
+.IP
+Without \fB\-\-may\-exist\fR, attempting to bind a logical port that
+has already been bound is an error.  With \fB\-\-may\-exist\fR, this
+command does nothing if \fIlogical\-port\fR has already been bound to
+a chassis.
+.
+.IP "[\fB\-\-if\-exists\fR] \fBlport\-unbind\fR \fIlogical\-port\fR"
+Resets the binding of \fIlogical\-port\fR to \fINULL\fR.
+.IP
+Without \fB\-\-if\-exists\fR, attempting to unbind a logical port
+that is not bound is an error.  With \fB\-\-if\-exists\fR, attempting
+to unbind logical port that is not bound has no effect.
+.
+.so lib/db-ctl-base.man
+.SH "EXIT STATUS"
+.IP "0"
+Successful program execution.
+.IP "1"
+Usage, syntax, or configuration file error.
+.IP "2"
+The \fIbridge\fR argument to \fBbr\-exists\fR specified the name of a
+bridge that does not exist.
+.SH "SEE ALSO"
+.
+.BR ovn\-sb (5).
diff --git a/ovn/utilities/ovn-sbctl.c b/ovn/utilities/ovn-sbctl.c
new file mode 100644 (file)
index 0000000..cbde60a
--- /dev/null
@@ -0,0 +1,872 @@
+/*
+ * Copyright (c) 2015 Nicira, Inc.
+ *
+ * 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 <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "db-ctl-base.h"
+
+#include "command-line.h"
+#include "compiler.h"
+#include "dynamic-string.h"
+#include "fatal-signal.h"
+#include "json.h"
+#include "ovsdb-data.h"
+#include "ovsdb-idl.h"
+#include "poll-loop.h"
+#include "process.h"
+#include "sset.h"
+#include "shash.h"
+#include "table.h"
+#include "timeval.h"
+#include "util.h"
+#include "openvswitch/vlog.h"
+#include "ovn/lib/ovn-sb-idl.h"
+
+VLOG_DEFINE_THIS_MODULE(sbctl);
+
+struct sbctl_context;
+
+/* --db: The database server to contact. */
+static const char *db;
+
+/* --oneline: Write each command's output as a single line? */
+static bool oneline;
+
+/* --dry-run: Do not commit any changes. */
+static bool dry_run;
+
+/* --timeout: Time to wait for a connection to 'db'. */
+static int timeout;
+
+/* Format for table output. */
+static struct table_style table_style = TABLE_STYLE_DEFAULT;
+
+/* The IDL we're using and the current transaction, if any.
+ * This is for use by sbctl_exit() only, to allow it to clean up.
+ * Other code should use its context arguments. */
+static struct ovsdb_idl *the_idl;
+static struct ovsdb_idl_txn *the_idl_txn;
+OVS_NO_RETURN static void sbctl_exit(int status);
+
+static void sbctl_cmd_init(void);
+OVS_NO_RETURN static void usage(void);
+static void parse_options(int argc, char *argv[], struct shash *local_options);
+static void run_prerequisites(struct ctl_command[], size_t n_commands,
+                              struct ovsdb_idl *);
+static void do_sbctl(const char *args, struct ctl_command *, size_t n,
+                     struct ovsdb_idl *);
+
+int
+main(int argc, char *argv[])
+{
+    extern struct vlog_module VLM_reconnect;
+    struct ovsdb_idl *idl;
+    struct ctl_command *commands;
+    struct shash local_options;
+    unsigned int seqno;
+    size_t n_commands;
+    char *args;
+
+    set_program_name(argv[0]);
+    fatal_ignore_sigpipe();
+    vlog_set_levels(NULL, VLF_CONSOLE, VLL_WARN);
+    vlog_set_levels(&VLM_reconnect, VLF_ANY_DESTINATION, VLL_WARN);
+    sbrec_init();
+
+    sbctl_cmd_init();
+
+    /* Log our arguments.  This is often valuable for debugging systems. */
+    args = process_escape_args(argv);
+    VLOG(ctl_might_write_to_db(argv) ? VLL_INFO : VLL_DBG, "Called as %s", args);
+
+    /* Parse command line. */
+    shash_init(&local_options);
+    parse_options(argc, argv, &local_options);
+    commands = ctl_parse_commands(argc - optind, argv + optind, &local_options,
+                                  &n_commands);
+
+    if (timeout) {
+        time_alarm(timeout);
+    }
+
+    /* Initialize IDL. */
+    idl = the_idl = ovsdb_idl_create(db, &sbrec_idl_class, false, false);
+    run_prerequisites(commands, n_commands, idl);
+
+    /* Execute the commands.
+     *
+     * 'seqno' is the database sequence number for which we last tried to
+     * execute our transaction.  There's no point in trying to commit more than
+     * once for any given sequence number, because if the transaction fails
+     * it's because the database changed and we need to obtain an up-to-date
+     * view of the database before we try the transaction again. */
+    seqno = ovsdb_idl_get_seqno(idl);
+    for (;;) {
+        ovsdb_idl_run(idl);
+        if (!ovsdb_idl_is_alive(idl)) {
+            int retval = ovsdb_idl_get_last_error(idl);
+            ctl_fatal("%s: database connection failed (%s)",
+                        db, ovs_retval_to_string(retval));
+        }
+
+        if (seqno != ovsdb_idl_get_seqno(idl)) {
+            seqno = ovsdb_idl_get_seqno(idl);
+            do_sbctl(args, commands, n_commands, idl);
+        }
+
+        if (seqno == ovsdb_idl_get_seqno(idl)) {
+            ovsdb_idl_wait(idl);
+            poll_block();
+        }
+    }
+}
+
+static void
+parse_options(int argc, char *argv[], struct shash *local_options)
+{
+    enum {
+        OPT_DB = UCHAR_MAX + 1,
+        OPT_ONELINE,
+        OPT_NO_SYSLOG,
+        OPT_DRY_RUN,
+        OPT_PEER_CA_CERT,
+        OPT_LOCAL,
+        OPT_COMMANDS,
+        OPT_OPTIONS,
+        VLOG_OPTION_ENUMS,
+        TABLE_OPTION_ENUMS
+    };
+    static const struct option global_long_options[] = {
+        {"db", required_argument, NULL, OPT_DB},
+        {"no-syslog", no_argument, NULL, OPT_NO_SYSLOG},
+        {"dry-run", no_argument, NULL, OPT_DRY_RUN},
+        {"oneline", no_argument, NULL, OPT_ONELINE},
+        {"timeout", required_argument, NULL, 't'},
+        {"help", no_argument, NULL, 'h'},
+        {"commands", no_argument, NULL, OPT_COMMANDS},
+        {"options", no_argument, NULL, OPT_OPTIONS},
+        {"version", no_argument, NULL, 'V'},
+        VLOG_LONG_OPTIONS,
+        TABLE_LONG_OPTIONS,
+        {NULL, 0, NULL, 0},
+    };
+    const int n_global_long_options = ARRAY_SIZE(global_long_options) - 1;
+    char *tmp, *short_options;
+
+    struct option *options;
+    size_t allocated_options;
+    size_t n_options;
+    size_t i;
+
+    tmp = ovs_cmdl_long_options_to_short_options(global_long_options);
+    short_options = xasprintf("+%s", tmp);
+    free(tmp);
+
+    /* We want to parse both global and command-specific options here, but
+     * getopt_long() isn't too convenient for the job.  We copy our global
+     * options into a dynamic array, then append all of the command-specific
+     * options. */
+    options = xmemdup(global_long_options, sizeof global_long_options);
+    allocated_options = ARRAY_SIZE(global_long_options);
+    n_options = n_global_long_options;
+    ctl_add_cmd_options(&options, &n_options, &allocated_options, OPT_LOCAL);
+    table_style.format = TF_LIST;
+
+    for (;;) {
+        int idx;
+        int c;
+
+        c = getopt_long(argc, argv, short_options, options, &idx);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case OPT_DB:
+            db = optarg;
+            break;
+
+        case OPT_ONELINE:
+            oneline = true;
+            break;
+
+        case OPT_NO_SYSLOG:
+            vlog_set_levels(&VLM_sbctl, VLF_SYSLOG, VLL_WARN);
+            break;
+
+        case OPT_DRY_RUN:
+            dry_run = true;
+            break;
+
+        case OPT_LOCAL:
+            if (shash_find(local_options, options[idx].name)) {
+                ctl_fatal("'%s' option specified multiple times",
+                            options[idx].name);
+            }
+            shash_add_nocopy(local_options,
+                             xasprintf("--%s", options[idx].name),
+                             optarg ? xstrdup(optarg) : NULL);
+            break;
+
+        case 'h':
+            usage();
+
+        case OPT_COMMANDS:
+            ctl_print_commands();
+
+        case OPT_OPTIONS:
+            ctl_print_options(global_long_options);
+
+        case 'V':
+            ovs_print_version(0, 0);
+            printf("DB Schema %s\n", sbrec_get_db_version());
+            exit(EXIT_SUCCESS);
+
+        case 't':
+            timeout = strtoul(optarg, NULL, 10);
+            if (timeout < 0) {
+                ctl_fatal("value %s on -t or --timeout is invalid",
+                            optarg);
+            }
+            break;
+
+        VLOG_OPTION_HANDLERS
+        TABLE_OPTION_HANDLERS(&table_style)
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+
+    if (!db) {
+        db = ctl_default_db();
+    }
+
+    for (i = n_global_long_options; options[i].name; i++) {
+        free(CONST_CAST(char *, options[i].name));
+    }
+    free(options);
+}
+
+static void
+usage(void)
+{
+    printf("\
+%s: ovs-vswitchd management utility\n\
+\n\
+for debugging and testing only, never use it in production\n\
+\n\
+usage: %s [OPTIONS] COMMAND [ARG...]\n\
+\n\
+SouthBound DB commands:\n\
+  show                        print overview of database contents\n\
+\n\
+Chassis commands:\n\
+  chassis-add CHASSIS ENCAP-TYPE ENCAP-IP  create a new chassis named\n\
+                                           CHASSIS with one encapsulation\n\
+                                           entry of ENCAP-TYPE and ENCAP-IP\n\
+  chassis-del CHASSIS         delete CHASSIS and all of its encaps,\n\
+                              and gateway_ports\n\
+\n\
+Port binding commands:\n\
+  lport-bind LPORT CHASSIS    bind logical port LPORT to CHASSIS\n\
+  lport-unbind LPORT          reset the port binding of logical port LPORT\n\
+\n\
+%s\
+\n\
+Options:\n\
+  --db=DATABASE               connect to DATABASE\n\
+                              (default: %s)\n\
+  -t, --timeout=SECS          wait at most SECS seconds for ovs-vswitchd\n\
+  --dry-run                   do not commit changes to database\n\
+  --oneline                   print exactly one line of output per command\n",
+           program_name, program_name, ctl_get_db_cmd_usage(), ctl_default_db());
+    vlog_usage();
+    printf("\
+  --no-syslog             equivalent to --verbose=sbctl:syslog:warn\n");
+    printf("\n\
+Other options:\n\
+  -h, --help                  display this help message\n\
+  -V, --version               display version information\n");
+    exit(EXIT_SUCCESS);
+}
+
+\f
+/* ovs-sbctl specific context.  Inherits the 'struct ctl_context' as base. */
+struct sbctl_context {
+    struct ctl_context base;
+
+    /* A cache of the contents of the database.
+     *
+     * A command that needs to use any of this information must first call
+     * sbctl_context_populate_cache().  A command that changes anything that
+     * could invalidate the cache must either call
+     * sbctl_context_invalidate_cache() or manually update the cache to
+     * maintain its correctness. */
+    bool cache_valid;
+    /* Maps from chassis name to struct sbctl_chassis. */
+    struct shash chassis;
+    /* Maps from lport name to struct sbctl_port_binding. */
+    struct shash port_bindings;
+};
+
+/* Casts 'base' into 'strcut sbctl_context'. */
+static struct sbctl_context *
+sbctl_context_cast(struct ctl_context *base)
+{
+    return CONTAINER_OF(base, struct sbctl_context, base);
+}
+
+struct sbctl_chassis {
+    const struct sbrec_chassis *ch_cfg;
+};
+
+struct sbctl_port_binding {
+    const struct sbrec_port_binding *bd_cfg;
+};
+
+static void
+sbctl_context_invalidate_cache(struct ctl_context *ctx)
+{
+    struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx);
+
+    if (!sbctl_ctx->cache_valid) {
+        return;
+    }
+    sbctl_ctx->cache_valid = false;
+    shash_destroy_free_data(&sbctl_ctx->chassis);
+    shash_destroy_free_data(&sbctl_ctx->port_bindings);
+}
+
+static void
+sbctl_context_populate_cache(struct ctl_context *ctx)
+{
+    struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx);
+    const struct sbrec_chassis *chassis_rec;
+    const struct sbrec_port_binding *port_binding_rec;
+    struct sset chassis, port_bindings;
+
+    if (sbctl_ctx->cache_valid) {
+        /* Cache is already populated. */
+        return;
+    }
+    sbctl_ctx->cache_valid = true;
+    shash_init(&sbctl_ctx->chassis);
+    shash_init(&sbctl_ctx->port_bindings);
+    sset_init(&chassis);
+    SBREC_CHASSIS_FOR_EACH(chassis_rec, ctx->idl) {
+        struct sbctl_chassis *ch;
+
+        if (!sset_add(&chassis, chassis_rec->name)) {
+            VLOG_WARN("database contains duplicate chassis name (%s)",
+                      chassis_rec->name);
+            continue;
+        }
+
+        ch = xmalloc(sizeof *ch);
+        ch->ch_cfg = chassis_rec;
+        shash_add(&sbctl_ctx->chassis, chassis_rec->name, ch);
+    }
+    sset_destroy(&chassis);
+
+    sset_init(&port_bindings);
+    SBREC_PORT_BINDING_FOR_EACH(port_binding_rec, ctx->idl) {
+        struct sbctl_port_binding *bd;
+
+        if (!sset_add(&port_bindings, port_binding_rec->logical_port)) {
+            VLOG_WARN("database contains duplicate port binding for logical "
+                      "port (%s)",
+                      port_binding_rec->logical_port);
+            continue;
+        }
+
+        bd = xmalloc(sizeof *bd);
+        bd->bd_cfg = port_binding_rec;
+        shash_add(&sbctl_ctx->port_bindings, port_binding_rec->logical_port,
+                  bd);
+    }
+    sset_destroy(&port_bindings);
+}
+
+static void
+check_conflicts(struct sbctl_context *sbctl_ctx, const char *name,
+                char *msg)
+{
+    if (shash_find(&sbctl_ctx->chassis, name)) {
+        ctl_fatal("%s because a chassis named %s already exists",
+                    msg, name);
+    }
+    free(msg);
+}
+
+static struct sbctl_chassis *
+find_chassis(struct sbctl_context *sbctl_ctx, const char *name,
+             bool must_exist)
+{
+    struct sbctl_chassis *sbctl_ch;
+
+    ovs_assert(sbctl_ctx->cache_valid);
+
+    sbctl_ch = shash_find_data(&sbctl_ctx->chassis, name);
+    if (must_exist && !sbctl_ch) {
+        ctl_fatal("no chassis named %s", name);
+    }
+
+    return sbctl_ch;
+}
+
+static struct sbctl_port_binding *
+find_port_binding(struct sbctl_context *sbctl_ctx, const char *name,
+                  bool must_exist)
+{
+    struct sbctl_port_binding *bd;
+
+    ovs_assert(sbctl_ctx->cache_valid);
+
+    bd = shash_find_data(&sbctl_ctx->port_bindings, name);
+    if (must_exist && !bd) {
+        ctl_fatal("no port named %s", name);
+    }
+
+    return bd;
+}
+
+static void
+pre_get_info(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &sbrec_chassis_col_name);
+    ovsdb_idl_add_column(ctx->idl, &sbrec_chassis_col_encaps);
+
+    ovsdb_idl_add_column(ctx->idl, &sbrec_encap_col_type);
+    ovsdb_idl_add_column(ctx->idl, &sbrec_encap_col_ip);
+
+    ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_logical_port);
+    ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
+}
+
+static struct cmd_show_table cmd_show_tables[] = {
+    {&sbrec_table_chassis,
+     &sbrec_chassis_col_name,
+     {&sbrec_chassis_col_encaps,
+      NULL,
+      NULL}},
+
+    {&sbrec_table_encap,
+     &sbrec_encap_col_type,
+     {&sbrec_encap_col_ip,
+      &sbrec_encap_col_options,
+      NULL}},
+
+    {NULL, NULL, {NULL, NULL, NULL}},
+};
+
+static void
+cmd_chassis_add(struct ctl_context *ctx)
+{
+    struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx);
+    struct sbrec_chassis *ch;
+    struct sbrec_encap *encap;
+    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+    const char *ch_name, *encap_type, *encap_ip;
+
+    ch_name = ctx->argv[1];
+    encap_type = ctx->argv[2];
+    encap_ip = ctx->argv[3];
+
+    sbctl_context_populate_cache(ctx);
+    if (may_exist) {
+        struct sbctl_chassis *sbctl_ch;
+
+        sbctl_ch = find_chassis(sbctl_ctx, ch_name, false);
+        if (sbctl_ch) {
+            return;
+        }
+    }
+    check_conflicts(sbctl_ctx, ch_name,
+                    xasprintf("cannot create a chassis named %s", ch_name));
+    ch = sbrec_chassis_insert(ctx->txn);
+    sbrec_chassis_set_name(ch, ch_name);
+    encap = sbrec_encap_insert(ctx->txn);
+    sbrec_encap_set_type(encap, encap_type);
+    sbrec_encap_set_ip(encap, encap_ip);
+    sbrec_chassis_set_encaps(ch, &encap, 1);
+    sbctl_context_invalidate_cache(ctx);
+}
+
+static void
+cmd_chassis_del(struct ctl_context *ctx)
+{
+    struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx);
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    struct sbctl_chassis *sbctl_ch;
+
+    sbctl_context_populate_cache(ctx);
+    sbctl_ch = find_chassis(sbctl_ctx, ctx->argv[1], must_exist);
+    if (sbctl_ch) {
+        if (sbctl_ch->ch_cfg) {
+            sbrec_chassis_delete(sbctl_ch->ch_cfg);
+        }
+        shash_find_and_delete(&sbctl_ctx->chassis, ctx->argv[1]);
+        free(sbctl_ch);
+    }
+}
+
+static void
+cmd_lport_bind(struct ctl_context *ctx)
+{
+    struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx);
+    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+    struct sbctl_chassis *sbctl_ch;
+    struct sbctl_port_binding *sbctl_bd;
+    char *lport_name, *ch_name;
+
+    /* port_binding must exist, chassis must exist! */
+    lport_name = ctx->argv[1];
+    ch_name = ctx->argv[2];
+
+    sbctl_context_populate_cache(ctx);
+    sbctl_bd = find_port_binding(sbctl_ctx, lport_name, true);
+    sbctl_ch = find_chassis(sbctl_ctx, ch_name, true);
+
+    if (sbctl_bd->bd_cfg->chassis) {
+        if (may_exist && sbctl_bd->bd_cfg->chassis == sbctl_ch->ch_cfg) {
+            return;
+        } else {
+            ctl_fatal("lport (%s) has already been binded to chassis (%s)",
+                      lport_name, sbctl_bd->bd_cfg->chassis->name);
+        }
+    }
+    sbrec_port_binding_set_chassis(sbctl_bd->bd_cfg, sbctl_ch->ch_cfg);
+    sbctl_context_invalidate_cache(ctx);
+}
+
+static void
+cmd_lport_unbind(struct ctl_context *ctx)
+{
+    struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx);
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    struct sbctl_port_binding *sbctl_bd;
+    char *lport_name;
+
+    lport_name = ctx->argv[1];
+    sbctl_context_populate_cache(ctx);
+    sbctl_bd = find_port_binding(sbctl_ctx, lport_name, must_exist);
+    if (sbctl_bd) {
+        sbrec_port_binding_set_chassis(sbctl_bd->bd_cfg, NULL);
+    }
+}
+
+\f
+static const struct ctl_table_class tables[] = {
+    {&sbrec_table_chassis,
+     {{&sbrec_table_chassis, &sbrec_chassis_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&sbrec_table_encap,
+     {{NULL, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&sbrec_table_logical_flow,
+     {{&sbrec_table_logical_flow, NULL,
+       &sbrec_logical_flow_col_logical_datapath},
+      {NULL, NULL, NULL}}},
+
+    {&sbrec_table_multicast_group,
+     {{NULL, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&sbrec_table_datapath_binding,
+     {{NULL, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&sbrec_table_port_binding,
+     {{&sbrec_table_port_binding, &sbrec_port_binding_col_logical_port, NULL},
+      {NULL, NULL, NULL}}},
+
+    {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}}
+};
+
+\f
+static void
+sbctl_context_init_command(struct sbctl_context *sbctl_ctx,
+                           struct ctl_command *command)
+{
+    ctl_context_init_command(&sbctl_ctx->base, command);
+}
+
+static void
+sbctl_context_init(struct sbctl_context *sbctl_ctx,
+                   struct ctl_command *command, struct ovsdb_idl *idl,
+                   struct ovsdb_idl_txn *txn,
+                   struct ovsdb_symbol_table *symtab)
+{
+    ctl_context_init(&sbctl_ctx->base, command, idl, txn, symtab,
+                     sbctl_context_invalidate_cache);
+    sbctl_ctx->cache_valid = false;
+}
+
+static void
+sbctl_context_done_command(struct sbctl_context *sbctl_ctx,
+                           struct ctl_command *command)
+{
+    ctl_context_done_command(&sbctl_ctx->base, command);
+}
+
+static void
+sbctl_context_done(struct sbctl_context *sbctl_ctx,
+                   struct ctl_command *command)
+{
+    ctl_context_done(&sbctl_ctx->base, command);
+}
+
+static void
+run_prerequisites(struct ctl_command *commands, size_t n_commands,
+                  struct ovsdb_idl *idl)
+{
+    struct ctl_command *c;
+
+    for (c = commands; c < &commands[n_commands]; c++) {
+        if (c->syntax->prerequisites) {
+            struct sbctl_context sbctl_ctx;
+
+            ds_init(&c->output);
+            c->table = NULL;
+
+            sbctl_context_init(&sbctl_ctx, c, idl, NULL, NULL);
+            (c->syntax->prerequisites)(&sbctl_ctx.base);
+            sbctl_context_done(&sbctl_ctx, c);
+
+            ovs_assert(!c->output.string);
+            ovs_assert(!c->table);
+        }
+    }
+}
+
+static void
+do_sbctl(const char *args, struct ctl_command *commands, size_t n_commands,
+         struct ovsdb_idl *idl)
+{
+    struct ovsdb_idl_txn *txn;
+    enum ovsdb_idl_txn_status status;
+    struct ovsdb_symbol_table *symtab;
+    struct sbctl_context sbctl_ctx;
+    struct ctl_command *c;
+    struct shash_node *node;
+    char *error = NULL;
+
+    txn = the_idl_txn = ovsdb_idl_txn_create(idl);
+    if (dry_run) {
+        ovsdb_idl_txn_set_dry_run(txn);
+    }
+
+    ovsdb_idl_txn_add_comment(txn, "ovs-sbctl: %s", args);
+
+    symtab = ovsdb_symbol_table_create();
+    for (c = commands; c < &commands[n_commands]; c++) {
+        ds_init(&c->output);
+        c->table = NULL;
+    }
+    sbctl_context_init(&sbctl_ctx, NULL, idl, txn, symtab);
+    for (c = commands; c < &commands[n_commands]; c++) {
+        sbctl_context_init_command(&sbctl_ctx, c);
+        if (c->syntax->run) {
+            (c->syntax->run)(&sbctl_ctx.base);
+        }
+        sbctl_context_done_command(&sbctl_ctx, c);
+
+        if (sbctl_ctx.base.try_again) {
+            sbctl_context_done(&sbctl_ctx, NULL);
+            goto try_again;
+        }
+    }
+    sbctl_context_done(&sbctl_ctx, NULL);
+
+    SHASH_FOR_EACH (node, &symtab->sh) {
+        struct ovsdb_symbol *symbol = node->data;
+        if (!symbol->created) {
+            ctl_fatal("row id \"%s\" is referenced but never created (e.g. "
+                        "with \"-- --id=%s create ...\")",
+                        node->name, node->name);
+        }
+        if (!symbol->strong_ref) {
+            if (!symbol->weak_ref) {
+                VLOG_WARN("row id \"%s\" was created but no reference to it "
+                          "was inserted, so it will not actually appear in "
+                          "the database", node->name);
+            } else {
+                VLOG_WARN("row id \"%s\" was created but only a weak "
+                          "reference to it was inserted, so it will not "
+                          "actually appear in the database", node->name);
+            }
+        }
+    }
+
+    status = ovsdb_idl_txn_commit_block(txn);
+    if (status == TXN_UNCHANGED || status == TXN_SUCCESS) {
+        for (c = commands; c < &commands[n_commands]; c++) {
+            if (c->syntax->postprocess) {
+                sbctl_context_init(&sbctl_ctx, c, idl, txn, symtab);
+                (c->syntax->postprocess)(&sbctl_ctx.base);
+                sbctl_context_done(&sbctl_ctx, c);
+            }
+        }
+    }
+    error = xstrdup(ovsdb_idl_txn_get_error(txn));
+
+    switch (status) {
+    case TXN_UNCOMMITTED:
+    case TXN_INCOMPLETE:
+        OVS_NOT_REACHED();
+
+    case TXN_ABORTED:
+        /* Should not happen--we never call ovsdb_idl_txn_abort(). */
+        ctl_fatal("transaction aborted");
+
+    case TXN_UNCHANGED:
+    case TXN_SUCCESS:
+        break;
+
+    case TXN_TRY_AGAIN:
+        goto try_again;
+
+    case TXN_ERROR:
+        ctl_fatal("transaction error: %s", error);
+
+    case TXN_NOT_LOCKED:
+        /* Should not happen--we never call ovsdb_idl_set_lock(). */
+        ctl_fatal("database not locked");
+
+    default:
+        OVS_NOT_REACHED();
+    }
+    free(error);
+
+    ovsdb_symbol_table_destroy(symtab);
+
+    for (c = commands; c < &commands[n_commands]; c++) {
+        struct ds *ds = &c->output;
+
+        if (c->table) {
+            table_print(c->table, &table_style);
+        } else if (oneline) {
+            size_t j;
+
+            ds_chomp(ds, '\n');
+            for (j = 0; j < ds->length; j++) {
+                int ch = ds->string[j];
+                switch (ch) {
+                case '\n':
+                    fputs("\\n", stdout);
+                    break;
+
+                case '\\':
+                    fputs("\\\\", stdout);
+                    break;
+
+                default:
+                    putchar(ch);
+                }
+            }
+            putchar('\n');
+        } else {
+            fputs(ds_cstr(ds), stdout);
+        }
+        ds_destroy(&c->output);
+        table_destroy(c->table);
+        free(c->table);
+
+        shash_destroy_free_data(&c->options);
+    }
+    free(commands);
+    ovsdb_idl_txn_destroy(txn);
+    ovsdb_idl_destroy(idl);
+
+    exit(EXIT_SUCCESS);
+
+try_again:
+    /* Our transaction needs to be rerun, or a prerequisite was not met.  Free
+     * resources and return so that the caller can try again. */
+    if (txn) {
+        ovsdb_idl_txn_abort(txn);
+        ovsdb_idl_txn_destroy(txn);
+        the_idl_txn = NULL;
+    }
+    ovsdb_symbol_table_destroy(symtab);
+    for (c = commands; c < &commands[n_commands]; c++) {
+        ds_destroy(&c->output);
+        table_destroy(c->table);
+        free(c->table);
+    }
+    free(error);
+}
+
+/* Frees the current transaction and the underlying IDL and then calls
+ * exit(status).
+ *
+ * Freeing the transaction and the IDL is not strictly necessary, but it makes
+ * for a clean memory leak report from valgrind in the normal case.  That makes
+ * it easier to notice real memory leaks. */
+static void
+sbctl_exit(int status)
+{
+    if (the_idl_txn) {
+        ovsdb_idl_txn_abort(the_idl_txn);
+        ovsdb_idl_txn_destroy(the_idl_txn);
+    }
+    ovsdb_idl_destroy(the_idl);
+    exit(status);
+}
+
+static const struct ctl_command_syntax sbctl_commands[] = {
+    /* Chassis commands. */
+    {"chassis-add", 3, 3, "CHASSIS ENCAP-TYPE ENCAP-IP", pre_get_info,
+     cmd_chassis_add, NULL, "--may-exist", RW},
+    {"chassis-del", 1, 1, "CHASSIS", pre_get_info, cmd_chassis_del, NULL,
+     "--if-exists", RW},
+
+    /* Port binding commands. */
+    {"lport-bind", 2, 2, "LPORT CHASSIS", pre_get_info, cmd_lport_bind, NULL,
+     "--may-exist", RW},
+    {"lport-unbind", 1, 1, "LPORT", pre_get_info, cmd_lport_unbind, NULL,
+     "--if-exists", RW},
+
+    /* SSL commands (To Be Added). */
+
+    {NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, RO},
+};
+
+/* Registers sbctl and common db commands. */
+static void
+sbctl_cmd_init(void)
+{
+    ctl_init(tables, cmd_show_tables, sbctl_exit);
+    ctl_register_commands(sbctl_commands);
+}
index 20bcb3f..bbcc792 100644 (file)
@@ -86,7 +86,8 @@ TESTSUITE_AT = \
        tests/vlog.at \
        tests/vtep-ctl.at \
        tests/auto-attach.at \
-       tests/ovn.at
+       tests/ovn.at \
+       tests/ovn-sbctl.at
 
 SYSTEM_KMOD_TESTSUITE_AT = \
        tests/system-common-macros.at \
@@ -107,7 +108,7 @@ SYSTEM_KMOD_TESTSUITE = $(srcdir)/tests/system-kmod-testsuite
 SYSTEM_USERSPACE_TESTSUITE = $(srcdir)/tests/system-userspace-testsuite
 DISTCLEANFILES += tests/atconfig tests/atlocal
 
-AUTOTEST_PATH = utilities:vswitchd:ovsdb:vtep:tests:$(PTHREAD_WIN32_DIR_DLL)
+AUTOTEST_PATH = utilities:vswitchd:ovsdb:vtep:tests:$(PTHREAD_WIN32_DIR_DLL):ovn:ovn/northd:ovn/utilities
 
 check-local: tests/atconfig tests/atlocal $(TESTSUITE)
        $(SHELL) '$(TESTSUITE)' -C tests AUTOTEST_PATH=$(AUTOTEST_PATH) $(TESTSUITEFLAGS)
diff --git a/tests/ovn-sbctl.at b/tests/ovn-sbctl.at
new file mode 100644 (file)
index 0000000..a8d0fe8
--- /dev/null
@@ -0,0 +1,61 @@
+AT_BANNER([ovn_controller_gw])
+
+# OVN_SBCTL_TEST_START
+m4_define([OVN_SBCTL_TEST_START],
+  [OVS_RUNDIR=`pwd`; export OVS_RUNDIR
+   OVS_LOGDIR=`pwd`; export OVS_LOGDIR
+   OVS_DBDIR=`pwd`; export OVS_DBDIR
+   OVS_SYSCONFDIR=`pwd`; export OVS_SYSCONFDIR
+
+   dnl Create databases (ovn-nb, ovn-sb).
+   for daemon in ovn-nb ovn-sb; do
+      AT_CHECK([ovsdb-tool create $daemon.db $abs_top_srcdir/${daemon%%-*}/${daemon}.ovsschema])
+   done
+
+   dnl Start ovsdb-server.
+   AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --log-file --remote=punix:$OVS_RUNDIR/db.sock ovn-nb.db ovn-sb.db], [0], [], [stderr])
+    ON_EXIT_UNQUOTED([kill `cat ovsdb-server.pid`])
+   AT_CHECK([[sed < stderr '
+/vlog|INFO|opened log file/d
+/ovsdb_server|INFO|ovsdb-server (Open vSwitch)/d']])
+   AT_CAPTURE_FILE([ovsdb-server.log])
+
+   dnl Start ovn-northd.
+   AT_CHECK([ovn-northd --detach --pidfile --log-file --ovnnb-db=unix:$OVS_RUNDIR/db.sock --ovnsb-db=unix:$OVS_RUNDIR/db.sock], [0], [], [stderr])
+   ON_EXIT_UNQUOTED([kill `cat ovn-northd.pid`])
+   AT_CHECK([[sed < stderr '
+/vlog|INFO|opened log file/d']])
+   AT_CAPTURE_FILE([ovn-northd.log])
+])
+
+# OVN_SBCTL_TEST_STOP
+m4_define([OVN_SBCTL_TEST_STOP],
+  [AT_CHECK([check_logs $1])
+   AT_CHECK([ovs-appctl -t ovn-northd exit])
+   AT_CHECK([ovs-appctl -t ovsdb-server exit])])
+
+# ovn-sbctl test.
+AT_SETUP([ovn-sbctl - test])
+OVN_SBCTL_TEST_START
+
+AT_CHECK([ovn-nbctl lswitch-add br-test])
+AT_CHECK([ovn-nbctl lport-add br-test vif0])
+AT_CHECK([ovn-nbctl lport-set-macs vif0 f0:ab:cd:ef:01:02])
+AT_CHECK([ovn-sbctl chassis-add ch0 stt 1.2.3.5])
+AT_CHECK([ovn-sbctl lport-bind vif0 ch0])
+
+AT_CHECK([ovn-sbctl show], [0], [dnl
+Chassis "ch0"
+    Encap stt
+        ip: "1.2.3.5"
+])
+
+uuid=$(ovn-sbctl --columns=_uuid list Chassis ch0 | cut -d ':' -f2 | tr -d ' ')
+AT_CHECK_UNQUOTED([ovn-sbctl --columns=logical_port,mac,chassis list Port_Binding], [0], [dnl
+logical_port        : "vif0"
+mac                 : [["f0:ab:cd:ef:01:02"]]
+chassis             : ${uuid}
+])
+
+OVN_SBCTL_TEST_STOP
+AT_CLEANUP
index 92b788b..f706f67 100644 (file)
@@ -68,3 +68,4 @@ m4_include([tests/vlog.at])
 m4_include([tests/vtep-ctl.at])
 m4_include([tests/auto-attach.at])
 m4_include([tests/ovn.at])
+m4_include([tests/ovn-sbctl.at])