[ovs-dev] [ovn-controller-gw 3/4] ovn-sbctl: Add ovn-sbctl.

Alex Wang alexw at nicira.com
Fri Jun 26 16:00:11 UTC 2015


Hey Ben, Russell,

Want to discuss if we may want to refine ovn-nbctl to the same format as
ovn-sbctl
(using lib/db-ctl-cmds module).

The benefit of such refinement is that ovn-nbctl can have the common ovsdb
operations
(e.g. list, set, create, ... like in ovs-vsctl).

Since my testing does not require much from ovn-nb, and the ovn-nbctl is
really well-written,
I did not conduct the refinement in this series.  But if you also think it
makes sense to do so,
I'd like to make the change~

Thanks,
Alex Wang,

On Fri, Jun 26, 2015 at 8:45 AM, Alex Wang <alexw at nicira.com> wrote:

> This commit adds ovn-sbctl to ovn family by using the db-ctl-base
> library.
>
> Signed-off-by: Alex Wang <alexw at nicira.com>
> ---
>  manpages.mk        |   12 +
>  ovn/.gitignore     |    2 +
>  ovn/automake.mk    |    9 +
>  ovn/ovn-sbctl.8.in |  159 ++++++++++
>  ovn/ovn-sbctl.c    |  842
> ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  tests/automake.mk  |    5 +-
>  tests/ovn-sbctl.at |   61 ++++
>  tests/testsuite.at |    1 +
>  8 files changed, 1089 insertions(+), 2 deletions(-)
>  create mode 100644 ovn/ovn-sbctl.8.in
>  create mode 100644 ovn/ovn-sbctl.c
>  create mode 100644 tests/ovn-sbctl.at
>
> diff --git a/manpages.mk b/manpages.mk
> index 3cec260..032cb26 100644
> --- a/manpages.mk
> +++ b/manpages.mk
> @@ -1,5 +1,17 @@
>  # Generated automatically -- do not modify!    -*- buffer-read-only: t -*-
>
> +ovn/ovn-sbctl.8: \
> +       ovn/ovn-sbctl.8.in \
> +       lib/db-ctl-base.man \
> +       lib/table.man \
> +       ovsdb/remote-active.man \
> +       ovsdb/remote-passive.man
> +ovn/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 \
> diff --git a/ovn/.gitignore b/ovn/.gitignore
> index 4c13616..2d4835a 100644
> --- a/ovn/.gitignore
> +++ b/ovn/.gitignore
> @@ -7,3 +7,5 @@
>  /ovn-sb.pic
>  /ovn-nbctl
>  /ovn-nbctl.8
> +/ovn-sbctl
> +/ovn-sbctl.8
> diff --git a/ovn/automake.mk b/ovn/automake.mk
> index 459ee36..6d2063f 100644
> --- a/ovn/automake.mk
> +++ b/ovn/automake.mk
> @@ -79,6 +79,15 @@ bin_PROGRAMS += ovn/ovn-nbctl
>  ovn_ovn_nbctl_SOURCES = ovn/ovn-nbctl.c
>  ovn_ovn_nbctl_LDADD = ovn/lib/libovn.la ovsdb/libovsdb.la lib/
> libopenvswitch.la
>
> +# ovn-sbctl
> +bin_PROGRAMS += ovn/ovn-sbctl
> +ovn_ovn_sbctl_SOURCES = ovn/ovn-sbctl.c
> +ovn_ovn_sbctl_LDADD = ovn/lib/libovn.la ovsdb/libovsdb.la lib/
> libopenvswitch.la
> +
> +MAN_ROOTS += ovn/ovn-sbctl.8.in
> +man_MANS += ovn/ovn-sbctl.8
> +DISTCLEANFILES += ovn/ovn-sbctl.8
> +
>  include ovn/controller/automake.mk
>  include ovn/lib/automake.mk
>  include ovn/northd/automake.mk
> diff --git a/ovn/ovn-sbctl.8.in b/ovn/ovn-sbctl.8.in
> new file mode 100644
> index 0000000..0cd317e
> --- /dev/null
> +++ b/ovn/ovn-sbctl.8.in
> @@ -0,0 +1,159 @@
> +.\" -*- 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 \fBovn\-sbctl\fR program configures \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 \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] \fBadd\-ch \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] \fBdel\-ch \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 "Binding Commands"
> +.
> +These commands manipulate \fBOVN_Southbound\fR bindings.
> +.
> +.IP "[\fB\-\-may\-exist\fR] \fBbind\-lport \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 binded is an error.  With \fB\-\-may\-exist\fR, this
> +command does nothing if \fIlogical\-port\fR has already been binded to
> +a chassis.
> +.
> +.IP "[\fB\-\-if\-exists\fR] \fBunbind\-lport\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 binded is an error.  With \fB\-\-if\-exists\fR, attempting
> +to unbind logical port that is not binded 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 ovsdb\-server (1),
> +.BR ovs\-vswitchd (8),
> +.BR ovs\-vswitchd.conf.db (5).
> diff --git a/ovn/ovn-sbctl.c b/ovn/ovn-sbctl.c
> new file mode 100644
> index 0000000..c433572
> --- /dev/null
> +++ b/ovn/ovn-sbctl.c
> @@ -0,0 +1,842 @@
> +/*
> + * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 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 "ovn/lib/ovn-sb-idl.h"
> +#include "table.h"
> +#include "timeval.h"
> +#include "util.h"
> +#include "openvswitch/vlog.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;
> +
> +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\
> +usage: %s [OPTIONS] COMMAND [ARG...]\n\
> +\n\
> +SouthBound DB commands:\n\
> +  show                        print overview of database contents\n\
> +\n\
> +Chassis commands:\n\
> +  add-ch CHASSIS              create a new chassis named CHASSIS\n\
> +  del-ch CHASSIS              delete CHASSIS and all of its encaps,\n\
> +                              and gateway_ports\n\
> +\n\
> +Encap commands:\n\
> +  add-encap CHASSIS ENCAP-TYPE ENCAP-IP  create new encap on CHASSIS\n\
> +  del-encap CHASSIS [ENCAP-TYPE] [ENCAP-IP] delete specified or all
> encaps\n\
> +                                            from CHASSIS\n\
> +\n\
> +Binding commands:\n\
> +  bind-lport LPORT CHASSIS    bind logical port LPORT to CHASSIS\n\
> +  unbind-lport LPORT          delete the 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);
> +}
> +
> +
> +/* 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;
> +    struct shash chassis;     /* Maps from chassis name to struct
> sbctl_chassis. */
> +    struct shash bindings;    /* Maps from lport name to struct
> sbctl_binding. */
> +};
> +
> +/* 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_binding {
> +    const struct sbrec_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->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_binding *binding_rec;
> +    struct sset chassis, 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->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(&bindings);
> +    SBREC_BINDING_FOR_EACH(binding_rec, ctx->idl) {
> +        struct sbctl_binding *bd;
> +
> +        if (!sset_add(&bindings, binding_rec->logical_port)) {
> +            VLOG_WARN("database contains duplicate binding for logical "
> +                      "port (%s)",
> +                      binding_rec->logical_port);
> +            continue;
> +        }
> +
> +        bd = xmalloc(sizeof *bd);
> +        bd->bd_cfg = binding_rec;
> +        shash_add(&sbctl_ctx->bindings, binding_rec->logical_port, bd);
> +    }
> +    sset_destroy(&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_binding *
> +find_binding(struct sbctl_context *sbctl_ctx, const char *name, bool
> must_exist)
> +{
> +    struct sbctl_binding *bd;
> +
> +    ovs_assert(sbctl_ctx->cache_valid);
> +
> +    bd = shash_find_data(&sbctl_ctx->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_chassis_col_gateway_ports);
> +
> +    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_binding_col_logical_port);
> +    ovsdb_idl_add_column(ctx->idl, &sbrec_binding_col_chassis);
> +}
> +
> +struct cmd_show_table cmd_show_tables[] = {
> +    {&sbrec_table_chassis,
> +     &sbrec_chassis_col_name,
> +     {&sbrec_chassis_col_encaps,
> +      NULL,
> +      NULL},
> +     false},
> +
> +    {&sbrec_table_encap,
> +     &sbrec_encap_col_type,
> +     {&sbrec_encap_col_ip,
> +      &sbrec_encap_col_options,
> +      NULL},
> +     false},
> +
> +    {NULL, NULL, {NULL, NULL, NULL}, false},
> +};
> +
> +static void
> +cmd_add_ch(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_del_ch(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_bind_lport(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_binding *sbctl_bd;
> +    char *lport_name, *ch_name;
> +
> +    /* binding must exist, chassis must exist! */
> +    lport_name = ctx->argv[1];
> +    ch_name = ctx->argv[2];
> +
> +    sbctl_context_populate_cache(ctx);
> +    sbctl_bd = find_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_binding_set_chassis(sbctl_bd->bd_cfg, sbctl_ch->ch_cfg);
> +    sbctl_context_invalidate_cache(ctx);
> +}
> +
> +static void
> +cmd_unbind_lport(struct ctl_context *ctx)
> +{
> +    struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx);
> +    bool must_exist = !shash_find(&ctx->options, "--if-exists");
> +    struct sbctl_binding *sbctl_bd;
> +    char *lport_name;
> +
> +    lport_name = ctx->argv[1];
> +    sbctl_context_populate_cache(ctx);
> +    sbctl_bd = find_binding(sbctl_ctx, lport_name, must_exist);
> +    if (sbctl_bd) {
> +        sbrec_binding_set_chassis(sbctl_bd->bd_cfg, NULL);
> +    }
> +}
> +
> +
> +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_gateway,
> +     {{&sbrec_table_gateway, NULL, NULL},
> +      {NULL, NULL, NULL}}},
> +
> +    {&sbrec_table_pipeline,
> +     {{&sbrec_table_pipeline, NULL, &sbrec_pipeline_col_logical_datapath},
> +      {NULL, NULL, NULL}}},
> +
> +    {&sbrec_table_binding,
> +     {{&sbrec_table_binding, &sbrec_binding_col_logical_port, NULL},
> +      {NULL, NULL, NULL}}},
> +
> +    {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}}
> +};
> +
> +
> +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);
> +}
> +
> +static const struct ctl_command_syntax sbctl_commands[] = {
> +    /* Chassis commands. */
> +    {"add-ch", 3, 3, "CHASSIS ENCAP-TYPE ENCAP-IP", pre_get_info,
> cmd_add_ch, NULL, "--may-exist",
> +     RW},
> +    {"del-ch", 1, 1, "CHASSIS", pre_get_info, cmd_del_ch, NULL,
> "--if-exists",
> +     RW},
> +
> +    /* Binding commands. */
> +    {"bind-lport", 2, 2, "LPORT CHASSIS", pre_get_info, cmd_bind_lport,
> NULL,
> +     "--may-exist", RW},
> +    {"unbind-lport", 1, 1, "LPORT", pre_get_info, cmd_unbind_lport, 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();
> +    ctl_register_commands(sbctl_commands);
> +}
> diff --git a/tests/automake.mk b/tests/automake.mk
> index 153d4e1..239b247 100644
> --- a/tests/automake.mk
> +++ b/tests/automake.mk
> @@ -83,7 +83,8 @@ TESTSUITE_AT = \
>         tests/vlog.at \
>         tests/vtep-ctl.at \
>         tests/auto-attach.at \
> -       tests/ovn.at
> +       tests/ovn.at \
> +       tests/ovn-sbctl.at
>
>  KMOD_TESTSUITE_AT = \
>         tests/kmod-testsuite.at \
> @@ -95,7 +96,7 @@ TESTSUITE_PATCH = $(srcdir)/tests/testsuite.patch
>  KMOD_TESTSUITE = $(srcdir)/tests/kmod-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
>
>  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
> index 0000000..9232ebb
> --- /dev/null
> +++ b/tests/ovn-sbctl.at
> @@ -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 "ovsdb-server.log ovn-northd.log" $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 add-ch ch0 stt 1.2.3.5])
> +AT_CHECK([ovn-sbctl bind-lport 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
> Binding], [0], [dnl
> +logical_port        : "vif0"
> +mac                 : [["f0:ab:cd:ef:01:02"]]
> +chassis             : ${uuid}
> +])
> +
> +OVN_SBCTL_TEST_STOP
> +AT_CLEANUP
> diff --git a/tests/testsuite.at b/tests/testsuite.at
> index 92b788b..1746efd 100644
> --- a/tests/testsuite.at
> +++ b/tests/testsuite.at
> @@ -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])
> \ No newline at end of file
> --
> 1.7.9.5
>
>



More information about the dev mailing list