From patchwork Thu Apr 1 23:20:50 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ben Pfaff X-Patchwork-Id: 1461449 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::136; helo=smtp3.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Received: from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4FBK4N3jBDz9sV5 for ; Fri, 2 Apr 2021 10:23:32 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 36FDF60DF9; Thu, 1 Apr 2021 23:22:30 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id sz4WitZu1IgH; Thu, 1 Apr 2021 23:22:18 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp3.osuosl.org (Postfix) with ESMTP id 6E77D60DDA; Thu, 1 Apr 2021 23:22:16 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 38C7BC000A; Thu, 1 Apr 2021 23:22:16 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136]) by lists.linuxfoundation.org (Postfix) with ESMTP id 17459C000B for ; Thu, 1 Apr 2021 23:22:14 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 9D0C860D5F for ; Thu, 1 Apr 2021 23:21:41 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id tq9P0fs-Uk32 for ; Thu, 1 Apr 2021 23:21:31 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.8.0 Received: from relay5-d.mail.gandi.net (relay5-d.mail.gandi.net [217.70.183.197]) by smtp3.osuosl.org (Postfix) with ESMTPS id 5884560D53 for ; Thu, 1 Apr 2021 23:21:30 +0000 (UTC) X-Originating-IP: 75.54.222.30 Received: from sigfpe.attlocal.net (75-54-222-30.lightspeed.rdcyca.sbcglobal.net [75.54.222.30]) (Authenticated sender: blp@ovn.org) by relay5-d.mail.gandi.net (Postfix) with ESMTPSA id 86D491C0006; Thu, 1 Apr 2021 23:21:27 +0000 (UTC) From: Ben Pfaff To: dev@openvswitch.org Date: Thu, 1 Apr 2021 16:20:50 -0700 Message-Id: <20210401232108.3902274-9-blp@ovn.org> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20210401232108.3902274-1-blp@ovn.org> References: <20210401232108.3902274-1-blp@ovn.org> MIME-Version: 1.0 Cc: Ben Pfaff Subject: [ovs-dev] [PATCH v2 08/26] ovn-sbctl: Add daemon support. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Also rewrite the manpage and convert it to XML for consistency with ovn-nbctl, and add tests. Signed-off-by: Ben Pfaff --- NEWS | 4 +- manpages.mk | 17 - tests/ovn-sbctl.at | 76 +++-- utilities/automake.mk | 7 +- utilities/ovn-dbctl.c | 24 +- utilities/ovn-dbctl.h | 3 +- utilities/ovn-nbctl.c | 1 + utilities/ovn-sbctl.8.in | 317 ------------------ utilities/ovn-sbctl.8.xml | 580 +++++++++++++++++++++++++++++++++ utilities/ovn-sbctl.c | 670 +++++++------------------------------- 10 files changed, 783 insertions(+), 916 deletions(-) delete mode 100644 utilities/ovn-sbctl.8.in create mode 100644 utilities/ovn-sbctl.8.xml diff --git a/NEWS b/NEWS index 8b170bcba6fb..a98529ac4ebe 100644 --- a/NEWS +++ b/NEWS @@ -7,7 +7,9 @@ Post-v21.03.0 (This may take testing and tuning to be effective.) This version of OVN requires DDLog 0.36. - Introduce ovn-controller incremetal processing engine statistics - - ovn-nbctl daemon mode is no longer considered experimental. + - Utilities: + * ovn-nbctl daemon mode is no longer considered experimental. + * ovn-sbctl now also supports daemon mode. OVN v21.03.0 - 12 Mar 2021 ------------------------- diff --git a/manpages.mk b/manpages.mk index 44e544681424..3334b38f943d 100644 --- a/manpages.mk +++ b/manpages.mk @@ -10,20 +10,3 @@ lib/common-syn.man: lib/common.man: lib/ovs.tmac: -utilities/ovn-sbctl.8: \ - utilities/ovn-sbctl.8.in \ - lib/common.man \ - lib/db-ctl-base.man \ - lib/ovs.tmac \ - lib/ssl-bootstrap.man \ - lib/ssl.man \ - lib/table.man \ - lib/vlog.man -utilities/ovn-sbctl.8.in: -lib/common.man: -lib/db-ctl-base.man: -lib/ovs.tmac: -lib/ssl-bootstrap.man: -lib/ssl.man: -lib/table.man: -lib/vlog.man: diff --git a/tests/ovn-sbctl.at b/tests/ovn-sbctl.at index 2712cc15490c..9334762fd313 100644 --- a/tests/ovn-sbctl.at +++ b/tests/ovn-sbctl.at @@ -1,9 +1,14 @@ AT_BANNER([ovn-sbctl]) +OVS_START_SHELL_HELPERS # OVN_SBCTL_TEST_START m4_define([OVN_SBCTL_TEST_START], - [dnl Create databases (ovn-nb, ovn-sb). - AT_KEYWORDS([ovn]) + [AT_KEYWORDS([ovn]) + AT_CAPTURE_FILE([ovsdb-server.log]) + AT_CAPTURE_FILE([ovn-northd.log]) + ovn_sbctl_test_start $1]) +ovn_sbctl_test_start() { + 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}.ovsschema]) done @@ -15,27 +20,54 @@ m4_define([OVN_SBCTL_TEST_START], 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 --no-chdir --pidfile --log-file --ovnnb-db=unix:$OVS_RUNDIR/ovnnb_db.sock --ovnsb-db=unix:$OVS_RUNDIR/ovnsb_db.sock], [0], [], [stderr]) on_exit "kill `cat ovn-northd.pid`" AT_CHECK([[sed < stderr ' /vlog|INFO|opened log file/d']]) - AT_CAPTURE_FILE([ovn-northd.log]) -]) + + AS_CASE([$1], + [daemon], + [export OVN_SB_DAEMON=$(ovn-sbctl --pidfile --detach --no-chdir --log-file -vsocket_util:off) + on_exit "kill `cat ovn-sbctl.pid`"], + [direct], [], + [*], [AT_FAIL_IF(:)]) +} # OVN_SBCTL_TEST_STOP -m4_define([OVN_SBCTL_TEST_STOP], - [AT_CHECK([check_logs "$1"]) - OVS_APP_EXIT_AND_WAIT([ovn-northd]) - OVS_APP_EXIT_AND_WAIT_BY_TARGET([$OVS_RUNDIR/ovnnb_db.ctl], [$OVS_RUNDIR/ovnnb_db.pid]) - OVS_APP_EXIT_AND_WAIT_BY_TARGET([$OVS_RUNDIR/ovnsb_db.ctl], [$OVS_RUNDIR/ovnsb_db.pid])]) +m4_define([OVN_SBCTL_TEST_STOP], [ovn_sbctl_test_stop]) +ovn_sbctl_test_stop() { + AT_CHECK([check_logs "$1"]) + OVS_APP_EXIT_AND_WAIT([ovn-northd]) + OVS_APP_EXIT_AND_WAIT_BY_TARGET([$OVS_RUNDIR/ovnnb_db.ctl], [$OVS_RUNDIR/ovnnb_db.pid]) + OVS_APP_EXIT_AND_WAIT_BY_TARGET([$OVS_RUNDIR/ovnsb_db.ctl], [$OVS_RUNDIR/ovnsb_db.pid]) +} +OVS_END_SHELL_HELPERS + +# OVN_SBCTL_TEST(NAME, TITLE, COMMANDS) +m4_define([OVN_SBCTL_TEST], + [OVS_START_SHELL_HELPERS + $1() { + $3 + } + OVS_END_SHELL_HELPERS + + AT_SETUP([ovn-sbctl - $2 - direct]) + OVN_SBCTL_TEST_START direct + $1 + OVN_SBCTL_TEST_STOP + AT_CLEANUP + + AT_SETUP([ovn-sbctl - $2 - daemon]) + OVN_SBCTL_TEST_START daemon + $1 + OVN_SBCTL_TEST_STOP + AT_CLEANUP]) dnl --------------------------------------------------------------------- -AT_SETUP([ovn-sbctl - chassis commands]) -OVN_SBCTL_TEST_START +OVN_SBCTL_TEST([ovn_sbctl_chassis_commands], [ovn-sbctl - chassis commands], [ ovn_init_db ovn-sb AT_CHECK([ovn-sbctl chassis-add ch0 geneve 1.2.3.4]) @@ -61,16 +93,14 @@ AT_CHECK([ovn-sbctl -f csv -d bare --no-headings --columns ip,type list encap | 1.2.3.5,vxlan ]) -OVN_SBCTL_TEST_STOP as ovn-sb OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -AT_CLEANUP +as +]) dnl --------------------------------------------------------------------- -AT_SETUP([ovn-sbctl]) -OVN_SBCTL_TEST_START - +OVN_SBCTL_TEST([ovn_sbctl_commands], [ovn-sbctl], [ AT_CHECK([ovn-nbctl ls-add br-test]) AT_CHECK([ovn-nbctl lsp-add br-test vif0]) AT_CHECK([ovn-nbctl lsp-set-addresses vif0 f0:ab:cd:ef:01:02]) @@ -131,20 +161,14 @@ mac : [[]] type : vtep options : {vtep_logical_switch=l0, vtep_physical_switch=p0} ]) - -OVN_SBCTL_TEST_STOP -AT_CLEANUP +]) dnl --------------------------------------------------------------------- -AT_SETUP([ovn-sbctl - connection]) -OVN_SBCTL_TEST_START - +OVN_SBCTL_TEST([ovn_sbctl_connection], [ovn-sbctl - connection], [ AT_CHECK([ovn-sbctl --inactivity-probe=30000 set-connection ptcp:6641:127.0.0.1 punix:$OVS_RUNDIR/ovnsb_db.sock]) AT_CHECK([ovn-sbctl list connection | grep inactivity_probe], [0], [dnl inactivity_probe : 30000 inactivity_probe : 30000 ]) - -OVN_SBCTL_TEST_STOP -AT_CLEANUP +]) \ No newline at end of file diff --git a/utilities/automake.mk b/utilities/automake.mk index 50c0cfded018..a03892f2055a 100644 --- a/utilities/automake.mk +++ b/utilities/automake.mk @@ -14,7 +14,6 @@ man_MANS += \ utilities/ovn-appctl.8 MAN_ROOTS += \ - utilities/ovn-sbctl.8.in \ utilities/ovn-detrace.1.in # Docker drivers @@ -30,6 +29,7 @@ EXTRA_DIST += \ utilities/ovn-docker-overlay-driver.in \ utilities/ovn-docker-underlay-driver.in \ utilities/ovn-nbctl.8.xml \ + utilities/ovn-sbctl.8.xml \ utilities/ovn-ic-nbctl.8.xml \ utilities/ovn-ic-sbctl.8.xml \ utilities/ovn-appctl.8.xml \ @@ -79,7 +79,10 @@ utilities_ovn_nbctl_LDADD = lib/libovn.la $(OVSDB_LIBDIR)/libovsdb.la $(OVS_LIBD # ovn-sbctl bin_PROGRAMS += utilities/ovn-sbctl -utilities_ovn_sbctl_SOURCES = utilities/ovn-sbctl.c +utilities_ovn_sbctl_SOURCES = \ + utilities/ovn-dbctl.c \ + utilities/ovn-dbctl.h \ + utilities/ovn-sbctl.c utilities_ovn_sbctl_LDADD = lib/libovn.la $(OVSDB_LIBDIR)/libovsdb.la $(OVS_LIBDIR)/libopenvswitch.la # ovn-ic-nbctl diff --git a/utilities/ovn-dbctl.c b/utilities/ovn-dbctl.c index d815dc5c8c5f..ffe85ce6d5d9 100644 --- a/utilities/ovn-dbctl.c +++ b/utilities/ovn-dbctl.c @@ -327,7 +327,8 @@ enum { }; static char * OVS_WARN_UNUSED_RESULT -handle_main_loop_option(int opt, const char *arg, bool *handled) +handle_main_loop_option(const struct ovn_dbctl_options *dbctl_options, + int opt, const char *arg, bool *handled) { ovs_assert(handled); *handled = true; @@ -338,11 +339,16 @@ handle_main_loop_option(int opt, const char *arg, bool *handled) break; case OPT_NO_WAIT: + if (!dbctl_options->allow_wait) { + return xstrdup("--no-wait not supported"); + } wait_type = NBCTL_WAIT_NONE; break; case OPT_WAIT: - if (!strcmp(arg, "none")) { + if (!dbctl_options->allow_wait) { + return xstrdup("--wait not supported"); + } else if (!strcmp(arg, "none")) { wait_type = NBCTL_WAIT_NONE; } else if (!strcmp(arg, "sb")) { wait_type = NBCTL_WAIT_SB; @@ -355,6 +361,9 @@ handle_main_loop_option(int opt, const char *arg, bool *handled) break; case OPT_PRINT_WAIT_TIME: + if (!dbctl_options->allow_wait) { + return xstrdup("--print-wait-time not supported"); + } print_wait_time = true; break; @@ -486,7 +495,8 @@ apply_options_direct(const struct ovn_dbctl_options *dbctl_options, for (const struct ovs_cmdl_parsed_option *po = parsed_options; po < &parsed_options[n]; po++) { bool handled; - char *error = handle_main_loop_option(po->o->val, po->arg, &handled); + char *error = handle_main_loop_option(dbctl_options, + po->o->val, po->arg, &handled); if (error) { ctl_fatal("%s", error); } @@ -834,7 +844,8 @@ find_option_by_value(const struct option *options, int value) } static char * OVS_WARN_UNUSED_RESULT -server_parse_options(int argc, char *argv[], struct shash *local_options, +server_parse_options(const struct ovn_dbctl_options *dbctl_options, + int argc, char *argv[], struct shash *local_options, int *n_options_p) { static const struct option global_long_options[] = { @@ -865,7 +876,7 @@ server_parse_options(int argc, char *argv[], struct shash *local_options, } bool handled; - error = handle_main_loop_option(c, optarg, &handled); + error = handle_main_loop_option(dbctl_options, c, optarg, &handled); if (error) { goto out; } @@ -967,7 +978,8 @@ server_cmd_run(struct unixctl_conn *conn, int argc, const char **argv_, /* Parse commands & options. */ char *args = process_escape_args(argv); shash_init(&local_options); - error = server_parse_options(argc, argv, &local_options, &n_options); + error = server_parse_options(dbctl_options, + argc, argv, &local_options, &n_options); if (error) { unixctl_command_reply_error(conn, error); goto out; diff --git a/utilities/ovn-dbctl.h b/utilities/ovn-dbctl.h index 5accf3c5e028..a1fbede6b5ce 100644 --- a/utilities/ovn-dbctl.h +++ b/utilities/ovn-dbctl.h @@ -15,7 +15,7 @@ #ifndef OVN_DBCTL_H #define OVN_DBCTL_H 1 -/* ovn-nbctl infrastructure code. */ +/* Common code for ovn-sbctl and ovn-nbctl. */ #include #include "ovsdb-idl.h" @@ -31,6 +31,7 @@ enum nbctl_wait_type { struct ovn_dbctl_options { const char *db_version; /* Database schema version. */ const char *default_db; /* Default database remote. */ + bool allow_wait; /* Allow --wait and related options? */ /* Names of important environment variables. */ const char *options_env_var_name; /* OVN_??_OPTIONS. */ diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c index 42841bfb8890..ff7438a49d4b 100644 --- a/utilities/ovn-nbctl.c +++ b/utilities/ovn-nbctl.c @@ -5982,6 +5982,7 @@ main(int argc, char *argv[]) struct ovn_dbctl_options dbctl_options = { .db_version = nbrec_get_db_version(), .default_db = default_nb_db(), + .allow_wait = true, .options_env_var_name = "OVN_NBCTL_OPTIONS", .daemon_env_var_name = "OVN_NB_DAEMON", diff --git a/utilities/ovn-sbctl.8.in b/utilities/ovn-sbctl.8.in deleted file mode 100644 index 153e72e6c28d..000000000000 --- a/utilities/ovn-sbctl.8.in +++ /dev/null @@ -1,317 +0,0 @@ -.\" -*- nroff -*- -.so lib/ovs.tmac -.TH ovn\-sbctl 8 "@VERSION@" "OVN" "OVN 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 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" -The OVSDB database remote to contact. If the \fBOVN_SB_DB\fR -environment variable is set, its value is used as the default. -Otherwise, the default is \fBunix:@RUNDIR@/ovnsb_db.sock\fR, but this -default is unlikely to be useful outside of single-machine OVN test -environments. -.IP -\fIserver\fR may be an OVSDB active or passive connection method, -e.g. \fBssl:192.168.10.5:6640\fR, as described in \fBovsdb\fR(7). -. -.IP "\fB\-\-leader\-only\fR" -.IQ "\fB\-\-no\-leader\-only\fR" -By default, or with \fB\-\-leader\-only\fR, when the database server -is a clustered database, \fBovn\-sbctl\fR will avoid servers other -than the cluster leader. This ensures that any data that -\fBovn\-sbctl\fR reads and reports is up-to-date. With -\fB\-\-no\-leader\-only\fR, \fBovn\-sbctl\fR will use any server in -the cluster, which means that for read-only transactions it can report -and act on stale data (transactions that modify the database are -always serialized even with \fB\-\-no\-leader\-only\fR). Refer to -\fBUnderstanding Cluster Consistency\fR in \fBovsdb\fR(7) for more -information. -. -.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.) -. -.IP "\fBOVN_SBCTL_OPTIONS\fR" -User can set one or more options using \fBOVN_SBCTL_OPTIONS\fR environment -variable. Under the Bourne shell this might be done like this: -export \fBOVN_SBCTL_OPTIONS\fR"="--db=unix:sb1.ovsdb --no-leader-only". -However user can still over-ride environment options by passing different -options in cli. When the environment variable is no longer needed, unset it, -e.g.: unset \fBOVN_SBCTL_OPTIONS\fR" -. -.so lib/vlog.man -.so lib/common.man -. -.SS "Table Formatting Options" -These options control the format of output from the \fBlist\fR and -\fBfind\fR commands. -.so lib/table.man -. -.SS "Public Key Infrastructure Options" -.so lib/ssl-bootstrap.man -.so lib/ssl.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 "\fBinit\fR" -Initializes the database, if it is empty. If the database has already -been initialized, this command has no effect. -. -.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. \fIencap-type\fR is a -comma-separated list of tunnel types. The chassis will have -one encap entry for each specified tunnel type with \fIencap-ip\fR -as the destination IP for each. -.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. -. -.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] \fBlsp\-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] \fBlsp\-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. -. -.SS "Logical Flow Commands" -. -.IP "[\fB\-\-uuid\fR] [\fB\-\-ovs\fR[\fB=\fIremote\fR]] [\fB\-\-stats\fR] [\fB\-\-vflows\fR] \fBlflow\-list\fR [\fIlogical-datapath\fR] [\fIlflow\fR...]" -List logical flows. If \fIlogical-datapath\fR is specified, only list -flows for that logical datapath. The \fIlogical-datapath\fR may be -given as a UUID or as a datapath name (reporting an error if multiple -datapaths have the same name). -.IP -If at least one \fIlflow\fR is given, only matching logical flows, if -any, are listed. Each \fIlflow\fR may be specified as a UUID or the -first few characters of a UUID, optionally prefixed by \fB0x\fR. -(Because \fBovn\-controller\fR sets OpenFlow flow cookies to the first -32 bits of the corresponding logical flow's UUID, this makes it easy -to look up the logical flow that generated a particular OpenFlow -flow.) -.IP -If \fB\-\-uuid\fR is specified, the output includes the first 32 bits -of each logical flow's UUID. This makes it easier to find the -OpenFlow flows that correspond to a given logical flow. -.IP -If \fB\-\-ovs\fR is included, \fBovn\-sbctl\fR attempts to obtain and -display the OpenFlow flows that correspond to each OVN logical flow. -To do so, \fBovn\-sbctl\fR connects to \fIremote\fR (by default, -\fBunix:@RUNDIR@/br-int.mgmt\fR) over OpenFlow and retrieves the -flows. If \fIremote\fR is specified, it must be an active OpenFlow -connection method described in \fBovsdb\fR(7). Please see the -discussion of the similar \fB\-\-ovs\fR option in \fBovn-trace\fR(8) -for more information about the OpenFlow flow output. -.IP -By default, OpenFlow flow output includes only match and actions. Add -\fB\-\-stats\fR to include all OpenFlow information, such as packet -and byte counters, duration, and timeouts. -.IP -If \fB\-\-vflows\fR is included, other southbound database records directly -used for generating OpenFlow flows are also listed. This includes: -\fIport-bindings\fR, \fImac-bindings\fR, \fImulticast-groups\fR, -\fIchassis\fR. The \fB\-\-ovs\fR and \fB\-\-stats\fR can also be used in -conjunction with \fB\-\-vflows\fR. -. -.IP "[\fB\-\-uuid\fR] \fBdump\-flows\fR [\fIlogical-datapath\fR]" -Alias for \fBlflow\-list\fB. -. -.SS "Remote Connectivity Commands" -. -These commands manipulate the \fBconnections\fR column in the \fBSB_Global\fR -table and rows in the \fBConnection\fR table. When \fBovsdb\-server\fR -is configured to use the \fBconnections\fR column for OVSDB connections, -this allows the administrator to use \fBovn\-sbctl\fR to configure database -connections. -. -.IP "\fBget\-connection\fR" -Prints the configured connection(s). -. -.IP "\fBdel\-connection\fR" -Deletes the configured connection(s). -. -.IP "\fBset\-connection\fR [\fIaccess\-specifier\fR] \fItarget\fR\&..." -Sets the configured manager target or targets. Each \fItarget\fR may -may be an OVSDB active or passive connection method, -e.g. \fBpssl:6640\fR, as described in \fBovsdb\fR(7), -optionally preceded by an optional access-specifier (\fBread\-only\fR or -\fBread\-write\fR). -If provided, the effect of the access specifier persists for subsequent -targets until changed by another access specifier. -. -.SS "SSL Configuration" -When \fBovsdb\-server\fR is configured to connect using SSL, the -following parameters are required: -.TP -\fIprivate-key\fR -Specifies a PEM file containing the private key used for SSL connections. -.TP -\fIcertificate\fR -Specifies a PEM file containing a certificate, signed by the -certificate authority (CA) used by the connection peers, that -certifies the private key, identifying a trustworthy peer. -.TP -\fIca-cert\fR -Specifies a PEM file containing the CA certificate used to verify that -the connection peers are trustworthy. -.PP -These SSL settings apply to all SSL connections made by the southbound -database server. -. -.IP "\fBget\-ssl\fR" -Prints the SSL configuration. -. -.IP "\fBdel\-ssl\fR" -Deletes the current SSL configuration. -. -.IP "[\fB\-\-bootstrap\fR] \fBset\-ssl\fR \fIprivate-key\fR \fIcertificate\fR \fIca-cert\fR [\fIssl-protocol-list\fR [\fIssl-cipher-list\fR]]" -Sets the SSL configuration. The \fB\-\-bootstrap\fR option is described -below. -. -.ST "CA Certificate Bootstrap" -.PP -Ordinarily, all of the files named in the SSL configuration must exist -before SSL connectivity can be used. However, if the \fIca-cert\fR file -does not exist and the \fB\-\-bootstrap\fR -option is given, then \fBovsdb\-server\fR will attempt to obtain the -CA certificate from the target on its first SSL connection and -save it to the named PEM file. If it is successful, it will -immediately drop the connection and reconnect, and from then on all -SSL connections must be authenticated by a certificate signed by the -CA certificate thus obtained. -.PP -\fBThis option exposes the SSL connection to a man-in-the-middle -attack obtaining the initial CA certificate\fR, but it may be useful -for bootstrapping. -.PP -This option is only useful if the SSL peer sends its CA certificate -as part of the SSL certificate chain. The SSL protocol does not -require the controller to send the CA certificate. -. -.SS "Database Commands" -. -These commands query and modify the contents of \fBovsdb\fR tables. -They are a slight abstraction of the \fBovsdb\fR interface and as such -they operate at a lower level than other \fBovs\-sbctl\fR commands. -.PP -.ST "Identifying Tables, Records, and Columns" -.PP -Each of these commands has a \fItable\fR parameter to identify a table -within the database. Many of them also take a \fIrecord\fR parameter -that identifies a particular record within a table. The \fIrecord\fR -parameter may be the UUID for a record, and many tables offer -additional ways to identify records. Some commands also take -\fIcolumn\fR parameters that identify a particular field within the -records in a table. -.PP -For a list of tables and their columns, see \fBovn\-sb\fR(5) or -see the table listing from the \fB--help\fR option. -.PP -Record names must be specified in full and with correct -capitalization, except that UUIDs may be abbreviated to their first 4 -(or more) hex digits, as long as that is unique within the table. -Names of tables and columns are not case-sensitive, and \fB\-\fR and -\fB_\fR are treated interchangeably. Unique abbreviations of table -and column names are acceptable, e.g. \fBaddr\fR or \fBa\fR is -sufficient to identify the \fBAddress_Set\fR table. -. -.so lib/db-ctl-base.man -.SH "EXIT STATUS" -.IP "0" -Successful program execution. -.IP "1" -Usage, syntax, or configuration file error. -.SH "SEE ALSO" -. -.BR ovn\-sb (5). diff --git a/utilities/ovn-sbctl.8.xml b/utilities/ovn-sbctl.8.xml new file mode 100644 index 000000000000..4e6b21c47369 --- /dev/null +++ b/utilities/ovn-sbctl.8.xml @@ -0,0 +1,580 @@ + + +

Name

+

ovn-sbctl -- Open Virtual Network southbound db management utility

+ +

Synopsis

+

ovn-sbctl [options] command [arg...]

+ +

Description

+ +

+ The ovn-sbctl program configures the + OVN_Southbound database by providing a high-level interface + to its configuration database. See ovn-sb(5) for + comprehensive documentation of the database schema. +

+ +

+ ovn-sbctl connects to an ovsdb-server 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. +

+ +

+ ovn-sbctl can perform any number of commands in a single + run, implemented as a single atomic transaction against the database. +

+ +

+ The ovn-sbctl command line begins with global options (see + OPTIONS below for details). The global options are followed + by one or more commands. Each command should begin with -- + by itself as a command-line argument, to separate it from the following + commands. (The -- before the first command is optional.) + The command itself starts with command-specific options, if any, followed + by the command name and any arguments. +

+ +

Daemon Mode

+ +

+ When it is invoked in the most ordinary way, ovn-sbctl + connects to an OVSDB server that hosts the southbound database, retrieves + a partial copy of the database that is complete enough to do its work, + sends a transaction request to the server, and receives and processes the + server's reply. In common interactive use, this is fine, but if the + database is large, the step in which ovn-sbctl retrieves a + partial copy of the database can take a long time, which yields poor + performance overall. +

+ +

+ To improve performance in such a case, ovn-sbctl offers a + "daemon mode," in which the user first starts ovn-sbctl + running in the background and afterward uses the daemon to execute + operations. Over several ovn-sbctl command invocations, + this performs better overall because it retrieves a copy of the database + only once at the beginning, not once per program run. +

+ +

+ Use the --detach option to start an ovn-sbctl + daemon. With this option, ovn-sbctl prints the name of a + control socket to stdout. The client should save this name in + environment variable OVN_SB_DAEMON. Under the Bourne shell + this might be done like this: +

+ +
+      export OVN_SB_DAEMON=$(ovn-sbctl --pidfile --detach)
+    
+ +

+ When OVN_SB_DAEMON is set, ovn-sbctl + automatically and transparently uses the daemon to execute its commands. +

+ +

+ When the daemon is no longer needed, kill it and unset the environment + variable, e.g.: +

+ +
+      kill $(cat $OVN_RUNDIR/ovn-sbctl.pid)
+      unset OVN_SB_DAEMON
+    
+ +

+ When using daemon mode, an alternative to the OVN_SB_DAEMON + environment variable is to specify a path for the Unix socket. When + starting the ovn-sbctl daemon, specify the -u option with a + full path to the location of the socket file. Here is an exmple: +

+ +
+      ovn-sbctl --detach -u /tmp/mysock.ctl
+    
+ +

+ Then to connect to the running daemon, use the -u option + with the full path to the socket created when the daemon was started: +

+ +
+      ovn-sbctl -u /tmp/mysock.ctl show
+    
+ +

Daemon Commands

+ +

+ Daemon mode is internally implemented using the same mechanism used by + ovn-appctl. One may also use ovn-appctl + directly with the following commands: +

+ +
+
+ run [options] command + [arg...] [-- [options] + command [arg...] ...] +
+
+ Instructs the daemon process to run one or more ovn-sbctl + commands described above and reply with the results of running these + commands. Accepts the --no-wait, --wait, + --timeout, --dry-run, --oneline, + and the options described under Table Formatting Options + in addition to the the command-specific options. +
+ +
exit
+
Causes ovn-sbctl to gracefully terminate.
+
+ +

Options

+ +

+ The options listed below affect the behavior of ovn-sbctl 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 --. +

+ +

+ ovn-sbctl also accepts options from the + OVN_SBCTL_OPTIONS environment variable, in the same format as + on the command line. Options from the command line override those in the + environment. +

+ +
+
--db database
+
+ The OVSDB database remote to contact. If the OVN_SB_DB + environment variable is set, its value is used as the default. + Otherwise, the default is unix:@RUNDIR@/ovnsb_db.sock, but + this default is unlikely to be useful outside of single-machine OVN + test environments. +
+ +
--leader-only
+
--no-leader-only
+
+ By default, or with --leader-only, when the database + server is a clustered database, ovn-sbctl will avoid + servers other than the cluster leader. This ensures that any data that + ovn-sbctl reads and reports is up-to-date. With + --no-leader-only, ovn-sbctl will use any + server in the cluster, which means that for read-only transactions it + can report and act on stale data (transactions that modify the database + are always serialized even with --no-leader-only). Refer + to Understanding Cluster Consistency in + ovsdb(7) for more information. +
+ +
--shuffle-remotes
+
--no-shuffle-remotes
+
+ By default, or with --shuffle-remotes, when there are + multiple remotes specified in the OVSDB connection string specified by + --db or the OVN_SB_DB environment variable, the + order of the remotes will be shuffled before the client tries to + connect. The remotes will be shuffled only once to a new order before + the first connection attempt. The following retries, if any, will + follow the same new order. The default behavior is to make sure + clients of a clustered database can distribute evenly to all memembers + of the cluster. With --no-shuffle-remotes, + ovn-sbctl will use the original order specified in the + connection string to connect. This allows user to specify the + preferred order, which is particularly useful for testing. +
+ +
--no-syslog
+
+

+ By default, ovn-sbctl logs its arguments and the details + of any changes that it makes to the system log. This option disables + this logging. +

+ +

+ This option is equivalent to + --verbose=sbctl:syslog:warn. +

+
+ +
--oneline
+
+ 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 list or + find commands; see Table Formatting Options + below. +
+ +
--dry-run
+
+ Prevents ovn-sbctl from actually modifying the database. +
+ +
-t secs
+
--timeout=secs
+
+ By default, or with a secs of 0, + ovn-sbctl waits forever for a response from the database. + This option limits runtime to approximately secs seconds. + If the timeout expires, ovn-sbctl will exit with a + SIGALRM signal. (A timeout would normally happen only if + the database cannot be contacted, or if the system is overloaded.) +
+
+ +

Daemon Options

+ + +

Logging options

+ + +

Table Formatting Options

+ These options control the format of output from the list and + find commands. + + +

PKI Options

+

+ PKI configuration is required to use SSL for the connection to the + database. +

+ + + +

Other Options

+ + + +

Commands

+ +

+ The following sections describe the commands that ovn-sbctl + supports. +

+ +

OVN_Southbound Commands

+ +

+ These commands work with an OVN_Southbound database as a + whole. +

+ +
+
init
+
+ Initializes the database, if it is empty. If the database has already + been initialized, this command has no effect. +
+ +
show
+
+ Prints a brief overview of the database contents. +
+
+ +

Chassis Commands

+ +

+ These commands manipulate OVN_Southbound chassis. +

+ +
+
[--may-exist] chassis-add chassis encap-type encap-ip
+ +
+

+ Creates a new chassis named chassis. + encap-type is a comma-separated list of tunnel types. The + chassis will have one encap entry for each specified tunnel type with + encap-ip as the destination IP for each. +

+ +

+ 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 chassis already exists. +

+
+ +
[--if-exists] chassis-del chassis
+
+

+ Deletes chassis and its encaps and + gateway_ports. +

+ +

+ Without --if-exists, attempting to delete a chassis that + does not exist is an error. With --if-exists attempting + to delete a chassis that does not exist has no effect. +

+
+
+ +

Port Binding Commands

+ +

+ These commands manipulate OVN_Southbound port bindings. +

+ +
+
[--may-exist] lsp-bind logical-port chassis
+
+

+ Binds the logical port named logical-port to + chassis. +

+ +

+ Without --may-exist, attempting to bind a logical port + that has already been bound is an error. With + --may-exist, this command does nothing if + logical-port has already been bound to a chassis. +

+
+ +
[--if-exists] lsp-unbind logical-port
+
+

+ Removes the binding of logical-port. +

+ +

+ Without --if-exists, attempting to unbind a logical port + that is not bound is an error. With --if-exists, + attempting to unbind logical port that is not bound has no effect. +

+
+
+ +

Logical Flow Commands

+ +
+
[--uuid] [--ovs[=remote]] [--stats] [--vflows] lflow-list [logical-datapath] [lflow...]
+ +
+

+ List logical flows. If logical-datapath is specified, + only list flows for that logical datapath. The + logical-datapath may be given as a UUID or as a datapath + name (reporting an error if multiple datapaths have the same name). +

+ +

+ If at least one lflow is given, only matching logical + flows, if any, are listed. Each lflow may be specified as + a UUID or the first few characters of a UUID, optionally prefixed by + 0x. (Because ovn-controller sets OpenFlow + flow cookies to the first 32 bits of the corresponding logical flow's + UUID, this makes it easy to look up the logical flow that generated a + particular OpenFlow flow.) +

+ +

+ If --uuid is specified, the output includes the first 32 + bits of each logical flow's UUID. This makes it easier to find the + OpenFlow flows that correspond to a given logical flow. +

+ +

+ If --ovs is included, ovn-sbctl attempts to + obtain and display the OpenFlow flows that correspond to each OVN + logical flow. To do so, ovn-sbctl connects to + remote (by default, + unix:@RUNDIR@/br-int.mgmt) over OpenFlow and retrieves + the flows. If remote is specified, it must be an active + OpenFlow connection method described in ovsdb(7). + Please see the discussion of the similar --ovs option in + ovn-trace(8) for more information about the OpenFlow + flow output. +

+ +

+ By default, OpenFlow flow output includes only match and actions. + Add --stats to include all OpenFlow information, such as + packet and byte counters, duration, and timeouts. +

+ +

+ If --vflows is included, other southbound database + records directly used for generating OpenFlow flows are also + listed. This includes: port-bindings, + mac-bindings, multicast-groups, + chassis. The --ovs and --stats + can also be used in conjunction with --vflows. +

+
+ +
[--uuid] dump-flows [logical-datapath]
+
Alias for lflow-list.
+
+ +

Remote Connectivity Commands

+ +

+ These commands manipulate the connections column in the + SB_Global table and rows in the Connection + table. When ovsdb-server is configured to use the + connections column for OVSDB connections, this allows the + administrator to use \fBovn\-sbctl\fR to configure database connections. +

+ +
+
get-connection
+
+ Prints the configured connection(s). +
+ +
del-connection
+
+ Deletes the configured connection(s). +
+ +
[--inactivity-probe=msecs] set-connection target...
+
+ Sets the configured manager target or targets. Use + --inactivity-probe=msecs to override the + default idle connection inactivity probe time. Use 0 to disable + inactivity probes. +
+
+ +

SSL Configuration Commands

+

+ When ovsdb-server is configured to connect using SSL, the + following parameters are required: +

+ +
+
private-key
+
+ Specifies a PEM file containing the private key used for SSL + connections. +
+ +
certificate
+
+ Specifies a PEM file containing a certificate, signed by the + certificate authority (CA) used by the connection peers, that + certifies the private key, identifying a trustworthy peer. +
+ +
ca-cert
+
+ Specifies a PEM file containing the CA certificate used to verify that + the connection peers are trustworthy. +
+
+ +

+ These SSL settings apply to all SSL connections made by the southbound + database server. +

+ +
+
get-ssl
+
+ Prints the SSL configuration. +
+ +
del-ssl
+
+ Deletes the current SSL configuration. +
+ +
[--bootstrap] set-ssl + private-key certificate ca-cert + [ssl-protocol-list [ssl-cipher-list]]
+
+ Sets the SSL configuration. +
+
+ +

Database Commands

+

+ These commands query and modify the contents of ovsdb + tables. They are a slight abstraction of the ovsdb + interface and as such they operate at a lower level than other + ovn-sbctl commands. +

+ +

Identifying Tables, Records, and Columns

+ +

+ Each of these commands has a table parameter to identify a + table within the database. Many of them also take a record + parameter that identifies a particular record within a table. The + record parameter may be the UUID for a record, which may be + abbreviated to its first 4 (or more) hex digits, as long as that is + unique. Many tables offer additional ways to identify records. Some + commands also take column parameters that identify a + particular field within the records in a table. +

+ +

+ For a list of tables and their columns, see ovn-sb(5) or + see the table listing from the --help option. +

+ +

+ Record names must be specified in full and with correct capitalization, + except that UUIDs may be abbreviated to their first 4 (or more) hex + digits, as long as that is unique within the table. Names of tables and + columns are not case-sensitive, and - and _ are + treated interchangeably. Unique abbreviations of table and column names + are acceptable, e.g. d or dhcp is sufficient + to identify the DHCP_Options table. +

+ + + +

Environment

+ +
+
OVN_SB_DAEMON
+
+ If set, this should name the Unix domain socket for an + ovn-sbctl server process. See Daemon Mode, + above, for more information. +
+ +
OVN_SBCTL_OPTIONS
+
+ If set, a set of options for ovn-sbctl to apply + automatically, in the same form as on the command line. +
+ +
OVN_SB_DB
+
+ If set, the default database to contact when the --db + option is not used. +
+
+ +

Exit Status

+
+
0
+
Successful program execution.
+ +
1
+
Usage, syntax, or network error.
+
+ +

See Also

+ ovn-sb(5), + ovn-appctl(8). + +
diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c index e3aa7a68e680..197504fe4015 100644 --- a/utilities/ovn-sbctl.c +++ b/utilities/ovn-sbctl.c @@ -31,8 +31,10 @@ #include "command-line.h" #include "compiler.h" #include "db-ctl-base.h" +#include "daemon.h" #include "dirs.h" #include "fatal-signal.h" +#include "jsonrpc.h" #include "openvswitch/dynamic-string.h" #include "openvswitch/json.h" #include "openvswitch/ofp-actions.h" @@ -43,276 +45,45 @@ #include "openvswitch/vlog.h" #include "lib/ovn-sb-idl.h" #include "lib/ovn-util.h" +#include "memory.h" +#include "ovn-dbctl.h" #include "ovsdb-data.h" #include "ovsdb-idl.h" #include "openvswitch/poll-loop.h" #include "process.h" +#include "simap.h" #include "sset.h" #include "stream-ssl.h" #include "stream.h" #include "table.h" +#include "timer.h" #include "timeval.h" +#include "unixctl.h" #include "util.h" #include "svec.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 unsigned 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); - -/* --leader-only, --no-leader-only: Only accept the leader in a cluster. */ -static int leader_only = true; - -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 bool do_sbctl(const char *args, struct ctl_command *, size_t n, - struct ovsdb_idl *); - -int -main(int argc, char *argv[]) +static void +sbctl_add_base_prerequisites(struct ovsdb_idl *idl, + enum nbctl_wait_type wait_type OVS_UNUSED) { - struct ovsdb_idl *idl; - struct ctl_command *commands; - struct shash local_options; - unsigned int seqno; - size_t n_commands; - - ovn_set_program_name(argv[0]); - fatal_ignore_sigpipe(); - vlog_set_levels(NULL, VLF_CONSOLE, VLL_WARN); - vlog_set_levels_from_string_assert("reconnect:warn"); - - sbctl_cmd_init(); - - /* Check if options are set via env var. */ - char **argv_ = ovs_cmdl_env_parse_all(&argc, argv, - getenv("OVN_SBCTL_OPTIONS")); - - /* Parse command line. */ - char *args = process_escape_args(argv_); - shash_init(&local_options); - parse_options(argc, argv_, &local_options); - char *error = ctl_parse_commands(argc - optind, argv_ + optind, - &local_options, &commands, &n_commands); - if (error) { - ctl_fatal("%s", error); - } - VLOG(ctl_might_write_to_db(commands, n_commands) ? VLL_INFO : VLL_DBG, - "Called as %s", args); - - ctl_timeout_setup(timeout); - - /* Initialize IDL. */ - idl = the_idl = ovsdb_idl_create(db, &sbrec_idl_class, false, true); - ovsdb_idl_set_leader_only(idl, leader_only); - 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); - if (do_sbctl(args, commands, n_commands, idl)) { - break; - } - } - - if (seqno == ovsdb_idl_get_seqno(idl)) { - ovsdb_idl_wait(idl); - poll_block(); - } - } - - for (int i = 0; i < argc; i++) { - free(argv_[i]); - } - free(argv_); - free(args); - exit(EXIT_SUCCESS); + ovsdb_idl_add_table(idl, &sbrec_table_sb_global); } static void -parse_options(int argc, char *argv[], struct shash *local_options) +sbctl_pre_execute(struct ovsdb_idl *idl, struct ovsdb_idl_txn *txn, + enum nbctl_wait_type wait_type OVS_UNUSED) { - enum { - OPT_DB = UCHAR_MAX + 1, - OPT_ONELINE, - OPT_NO_SYSLOG, - OPT_DRY_RUN, - OPT_LOCAL, - OPT_COMMANDS, - OPT_OPTIONS, - OPT_BOOTSTRAP_CA_CERT, - VLOG_OPTION_ENUMS, - TABLE_OPTION_ENUMS, - SSL_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}, - {"leader-only", no_argument, &leader_only, true}, - {"no-leader-only", no_argument, &leader_only, false}, - {"version", no_argument, NULL, 'V'}, - VLOG_LONG_OPTIONS, - STREAM_SSL_LONG_OPTIONS, - {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT}, - 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); - - 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(&this_module, 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), - nullable_xstrdup(optarg)); - break; - - case 'h': - usage(); - - case OPT_COMMANDS: - ctl_print_commands(); - /* fall through */ - - case OPT_OPTIONS: - ctl_print_options(global_long_options); - /* fall through */ - - case 'V': - ovn_print_version(0, 0); - printf("DB Schema %s\n", sbrec_get_db_version()); - exit(EXIT_SUCCESS); - - case 't': - if (!str_to_uint(optarg, 10, &timeout) || !timeout) { - ctl_fatal("value %s on -t or --timeout is invalid", optarg); - } - break; - - VLOG_OPTION_HANDLERS - TABLE_OPTION_HANDLERS(&table_style) - STREAM_SSL_OPTION_HANDLERS - - case OPT_BOOTSTRAP_CA_CERT: - stream_ssl_set_ca_cert_file(optarg, true); - break; - - case '?': - exit(EXIT_FAILURE); - - default: - abort(); - - case 0: - break; - } - } - free(short_options); - - if (!db) { - db = default_sb_db(); - } - - for (i = n_global_long_options; options[i].name; i++) { - free(CONST_CAST(char *, options[i].name)); + const struct sbrec_sb_global *sb = sbrec_sb_global_first(idl); + if (!sb) { + /* XXX add verification that table is empty */ + sb = sbrec_sb_global_insert(txn); } - free(options); } static void -usage(void) +sbctl_usage(void) { printf("\ %s: OVN southbound DB management utility\n\ @@ -372,8 +143,12 @@ Other options:\n\ stream_usage("database", true, true, true); exit(EXIT_SUCCESS); } - +/* One should not use ctl_fatal() within commands because it will kill the + * daemon if we're in daemon mode. Use ctl_error() instead and return + * gracefully. */ +#define ctl_fatal dont_use_ctl_fatal_use_ctl_error_and_return + /* ovs-sbctl specific context. Inherits the 'struct ctl_context' as base. */ struct sbctl_context { struct ctl_context base; @@ -420,18 +195,20 @@ sbctl_context_invalidate_cache(struct ctl_context *ctx) shash_destroy_free_data(&sbctl_ctx->port_bindings); } -static void -sbctl_context_populate_cache(struct ctl_context *ctx) +/* Casts 'base' into 'struct sbctl_context' and initializes it if needed. */ +static struct sbctl_context * +sbctl_context_get(struct ctl_context *ctx) { - struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx); + struct sbctl_context *sbctl_ctx + = CONTAINER_OF(ctx, struct sbctl_context, base); + if (sbctl_ctx->cache_valid) { + return sbctl_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); @@ -468,46 +245,63 @@ sbctl_context_populate_cache(struct ctl_context *ctx) bd); } sset_destroy(&port_bindings); + + return sbctl_ctx; +} + +static struct ctl_context * +sbctl_ctx_create(void) +{ + struct sbctl_context *sbctx = xmalloc(sizeof *sbctx); + *sbctx = (struct sbctl_context) { + .cache_valid = false, + }; + return &sbctx->base; } static void -check_conflicts(struct sbctl_context *sbctl_ctx, const char *name, - char *msg) +sbctl_ctx_destroy(struct ctl_context *ctx) { + sbctl_context_invalidate_cache(ctx); + free(ctx); +} + +static bool +check_conflicts(struct ctl_context *ctx, const char *name, char *msg) +{ + struct sbctl_context *sbctl_ctx = sbctl_context_get(ctx); if (shash_find(&sbctl_ctx->chassis, name)) { - ctl_fatal("%s because a chassis named %s already exists", - msg, name); + ctl_error(&sbctl_ctx->base, + "%s because a chassis named %s already exists", + msg, name); + return false; } free(msg); + + return true; } static struct sbctl_chassis * -find_chassis(struct sbctl_context *sbctl_ctx, const char *name, - bool must_exist) +find_chassis(struct ctl_context *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); + struct sbctl_context *sbctl_ctx = sbctl_context_get(ctx); + struct sbctl_chassis *sbctl_ch = shash_find_data(&sbctl_ctx->chassis, + name); if (must_exist && !sbctl_ch) { - ctl_fatal("no chassis named %s", name); + ctl_error(ctx, "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) +find_port_binding(struct ctl_context *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); + struct sbctl_context *sbctl_ctx = sbctl_context_get(ctx); + struct sbctl_port_binding *bd = shash_find_data(&sbctl_ctx->port_bindings, + name); if (must_exist && !bd) { - ctl_fatal("no port named %s", name); + ctl_error(&sbctl_ctx->base, "no port named %s", name); } return bd; @@ -588,7 +382,6 @@ sbctl_init(struct ctl_context *ctx OVS_UNUSED) static void cmd_chassis_add(struct ctl_context *ctx) { - struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx); bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; const char *ch_name, *encap_types, *encap_ip; @@ -596,17 +389,17 @@ cmd_chassis_add(struct ctl_context *ctx) encap_types = 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); + struct sbctl_chassis *sbctl_ch = find_chassis(ctx, ch_name, false); if (sbctl_ch) { return; } } - check_conflicts(sbctl_ctx, ch_name, - xasprintf("cannot create a chassis named %s", ch_name)); + if (!check_conflicts(ctx, ch_name, + xasprintf("cannot create a chassis named %s", + ch_name))) { + return; + } struct sset encap_set; sset_from_delimited_string(&encap_set, encap_types, ","); @@ -642,8 +435,7 @@ cmd_chassis_del(struct ctl_context *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); + sbctl_ch = find_chassis(ctx, ctx->argv[1], must_exist); if (sbctl_ch) { if (sbctl_ch->ch_cfg) { size_t i; @@ -672,17 +464,21 @@ cmd_lsp_bind(struct ctl_context *ctx) 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); + sbctl_bd = find_port_binding(ctx, lport_name, true); + if (!sbctl_ctx) { + return; + } + sbctl_ch = find_chassis(ctx, ch_name, true); + if (!sbctl_ch) { + return; + } 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)", + if (!may_exist || sbctl_bd->bd_cfg->chassis != sbctl_ch->ch_cfg) { + ctl_error(ctx, "lport (%s) has already been binded to chassis (%s)", lport_name, sbctl_bd->bd_cfg->chassis->name); } + return; } sbrec_port_binding_set_chassis(sbctl_bd->bd_cfg, sbctl_ch->ch_cfg); sbrec_port_binding_set_up(sbctl_bd->bd_cfg, &up, 1); @@ -692,14 +488,12 @@ cmd_lsp_bind(struct ctl_context *ctx) static void cmd_lsp_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); + sbctl_bd = find_port_binding(ctx, lport_name, must_exist); if (sbctl_bd) { sbrec_port_binding_set_chassis(sbctl_bd->bd_cfg, NULL); sbrec_port_binding_set_up(sbctl_bd->bd_cfg, NULL, 0); @@ -1123,7 +917,9 @@ cmd_lflow_list(struct ctl_context *ctx) char *error = ctl_get_row(ctx, &sbrec_table_datapath_binding, ctx->argv[1], false, &row); if (error) { - ctl_fatal("%s", error); + ctl_error(ctx, "%s", error); + free(error); + return; } datapath = (const struct sbrec_datapath_binding *)row; @@ -1136,8 +932,9 @@ cmd_lflow_list(struct ctl_context *ctx) for (size_t i = 1; i < ctx->argc; i++) { char *s = parse_partial_uuid(ctx->argv[i]); if (!s) { - ctl_fatal("%s is not a UUID or the beginning of a UUID", + ctl_error(ctx, "%s is not a UUID or the beginning of a UUID", ctx->argv[i]); + return; } ctx->argv[i] = s; } @@ -1272,12 +1069,15 @@ sbctl_ip_mcast_flush(struct ctl_context *ctx) char *error = ctl_get_row(ctx, &sbrec_table_datapath_binding, ctx->argv[1], false, &row); if (error) { - ctl_fatal("%s", error); + ctl_error(ctx, "%s", error); + free(error); + return; } dp = (const struct sbrec_datapath_binding *)row; if (!dp) { - ctl_fatal("%s is not a valid datapath", ctx->argv[1]); + ctl_error(ctx, "%s is not a valid datapath", ctx->argv[1]); + return; } sbctl_ip_mcast_flush_switch(ctx, dp); @@ -1564,248 +1364,6 @@ static const struct ctl_table_class tables[SBREC_N_TABLES] = { = {&sbrec_load_balancer_col_name, 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) -{ - ovsdb_idl_add_table(idl, &sbrec_table_sb_global); - - for (struct ctl_command *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); - if (sbctl_ctx.base.error) { - ctl_fatal("%s", sbctl_ctx.base.error); - } - sbctl_context_done(&sbctl_ctx, c); - - ovs_assert(!c->output.string); - ovs_assert(!c->table); - } - } -} - -static bool -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; - - 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); - - const struct sbrec_sb_global *sb = sbrec_sb_global_first(idl); - if (!sb) { - /* XXX add verification that table is empty */ - sbrec_sb_global_insert(txn); - } - - 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); - } - if (sbctl_ctx.base.error) { - ctl_fatal("%s", sbctl_ctx.base.error); - } - 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); - if (sbctl_ctx.base.error) { - ctl_fatal("%s", sbctl_ctx.base.error); - } - sbctl_context_done(&sbctl_ctx, c); - } - } - } - - 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", ovsdb_idl_txn_get_error(txn)); - - case TXN_NOT_LOCKED: - /* Should not happen--we never call ovsdb_idl_set_lock(). */ - ctl_fatal("database not locked"); - - default: - OVS_NOT_REACHED(); - } - - 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); - - return true; - -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. */ - 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); - } - return false; -} - -/* 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[] = { { "init", 0, 0, "", NULL, sbctl_init, NULL, "", RW }, @@ -1850,11 +1408,31 @@ static const struct ctl_command_syntax sbctl_commands[] = { {NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, RO}, }; -/* Registers sbctl and common db commands. */ -static void -sbctl_cmd_init(void) +int +main(int argc, char *argv[]) { - ctl_init(&sbrec_idl_class, sbrec_table_classes, tables, - cmd_show_tables, sbctl_exit); - ctl_register_commands(sbctl_commands); + struct ovn_dbctl_options dbctl_options = { + .db_version = sbrec_get_db_version(), + .default_db = default_sb_db(), + .allow_wait = false, + + .options_env_var_name = "OVN_SBCTL_OPTIONS", + .daemon_env_var_name = "OVN_SB_DAEMON", + + .idl_class = &sbrec_idl_class, + .tables = tables, + .cmd_show_table = cmd_show_tables, + .commands = sbctl_commands, + + .usage = sbctl_usage, + .add_base_prerequisites = sbctl_add_base_prerequisites, + .pre_execute = sbctl_pre_execute, + .post_execute = NULL, + + .ctx_create = sbctl_ctx_create, + .ctx_destroy = sbctl_ctx_destroy, + }; + + return ovn_dbctl_main(argc, argv, &dbctl_options); } +