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 <al...@nicira.com> wrote: > This commit adds ovn-sbctl to ovn family by using the db-ctl-base > library. > > Signed-off-by: Alex Wang <al...@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 > > _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev