diff --git a/manpages.mk b/manpages.mk index 3cec26018f8..6e2853b63a8 100644 --- a/manpages.mk +++ b/manpages.mk @@ -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 \ diff --git a/ovn/utilities/.gitignore b/ovn/utilities/.gitignore index 8769651fead..5832b6c2b28 100644 --- a/ovn/utilities/.gitignore +++ b/ovn/utilities/.gitignore @@ -1,3 +1,5 @@ /ovn-ctl.8 /ovn-nbctl /ovn-nbctl.8 +/ovn-sbctl +/ovn-sbctl.8 diff --git a/ovn/utilities/automake.mk b/ovn/utilities/automake.mk index 145ee44f04a..b247a542476 100644 --- a/ovn/utilities/automake.mk +++ b/ovn/utilities/automake.mk @@ -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 index 00000000000..b5c796e2efc --- /dev/null +++ b/ovn/utilities/ovn-sbctl.8.in @@ -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 index 00000000000..cbde60ae1ed --- /dev/null +++ b/ovn/utilities/ovn-sbctl.c @@ -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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} + + +/* 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); + } +} + + +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}}} +}; + + +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); +} diff --git a/tests/automake.mk b/tests/automake.mk index 20bcb3f6d79..bbcc7922f21 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -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 index 00000000000..a8d0fe88bf8 --- /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 $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 diff --git a/tests/testsuite.at b/tests/testsuite.at index 92b788b9fda..f706f676041 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])