| Message ID | 20250811100925.917826-1-numans@ovn.org |
|---|---|
| State | Changes Requested |
| Headers | show |
| Series | OVN Bridge Controller service. | expand |
| Context | Check | Description |
|---|---|---|
| ovsrobot/apply-robot | warning | apply and check: warning |
| ovsrobot/github-robot-_Build_and_Test | fail | github build: failed |
| ovsrobot/github-robot-_ovn-kubernetes | fail | github build: failed |
Hi Numan, The first 11 patches in this series fail CI because of failures to `make rpm-fedora`. I think some of the specfile changes from patch 12 should be added to earlier patches in order to allow CI to succeed. I have more below. On 8/11/25 6:09 AM, numans@ovn.org wrote: > From: Numan Siddique <numans@ovn.org> > > This commit is the first of the series to add a new service > to OVN called ovn-br-controller (OVN bridge controller). > This commit adds > - a schema file > - a basic skeleton of ovn-br-controller service (which does nothing) > - ovn-brctl utility similar to ovn-nbctl/ovn-sbctl. > > Rational for adding this service: > There's a need to configure the provider bridges with specific OpenFlow > rules after packets leave the OVN pipeline and enters these provider > bridges via the patch ports. Since OVN has a very good abstraction > of the OpenFlow rules using OVN logical flows, we can leverage the same > here so that CMS doesn't have to deal with the specifics of OpenFlow > protocol. Also if the CMS is written in golang or other languages, > CMS has to mostly rely on ovs-vsctl/ovs-ofctl to program the flows. > Adding this support in OVN avoids the CMS to exec these utils to > add/delete and dump the existing OpenFlow rules. > > Any user can also use this new service to program and manage any OVS > bridge without using OVN and hence this service is named as > "ovn-br-controller." > > Signed-off-by: Numan Siddique <numans@ovn.org> > --- > Makefile.am | 5 +- > automake.mk | 36 ++ > br-controller/automake.mk | 8 + > br-controller/ovn-br-controller.8.xml | 5 + > br-controller/ovn-br-controller.c | 175 ++++++++ > lib/automake.mk | 17 +- > lib/ovn-br-idl.ann | 9 + > lib/ovn-util.c | 13 + > lib/ovn-util.h | 1 + > ovn-br.ovsschema | 94 +++++ > ovn-br.xml | 452 ++++++++++++++++++++ > tutorial/ovn-sandbox | 24 +- > utilities/automake.mk | 8 + > utilities/ovn-brctl.c | 585 ++++++++++++++++++++++++++ > 14 files changed, 1429 insertions(+), 3 deletions(-) > create mode 100644 br-controller/automake.mk > create mode 100644 br-controller/ovn-br-controller.8.xml > create mode 100644 br-controller/ovn-br-controller.c > create mode 100644 lib/ovn-br-idl.ann > create mode 100644 ovn-br.ovsschema > create mode 100644 ovn-br.xml > create mode 100644 utilities/ovn-brctl.c > > diff --git a/Makefile.am b/Makefile.am > index ea98fb5fb5..1936e9697a 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -115,7 +115,9 @@ EXTRA_DIST = \ > ovn-ic-nb.ovsschema \ > ovn-ic-nb.xml \ > ovn-ic-sb.ovsschema \ > - ovn-ic-sb.xml > + ovn-ic-sb.xml \ > + ovn-br.ovsschema \ > + ovn-br.xml > bin_PROGRAMS = > sbin_PROGRAMS = > bin_SCRIPTS = > @@ -507,4 +509,5 @@ include controller/automake.mk > include controller-vtep/automake.mk > include northd/automake.mk > include ic/automake.mk > +include br-controller/automake.mk > include build-aux/automake.mk > diff --git a/automake.mk b/automake.mk > index a7947a3f54..53860ad89b 100644 > --- a/automake.mk > +++ b/automake.mk > @@ -120,6 +120,35 @@ ovn-ic-sb.5: \ > $(srcdir)/ovn-ic-sb.xml > $@.tmp && \ > mv $@.tmp $@ > > +# OVN bridge controller E-R diagram > +# > +# If "python" or "dot" is not available, then we do not add graphical diagram > +# to the documentation. > +if HAVE_DOT > +ovn-br.gv: ${OVSDIR}/ovsdb/ovsdb-dot.in $(srcdir)/ovn-br.ovsschema > + $(AM_V_GEN)$(OVSDB_DOT) --no-arrows $(srcdir)/ovn-br.ovsschema > $@ > +ovn-br.pic: ovn-br.gv ${OVSDIR}/ovsdb/dot2pic > + $(AM_V_GEN)(dot -T plain < ovn-br.gv | $(PYTHON3) ${OVSDIR}/ovsdb/dot2pic -f 3) > $@.tmp && \ > + mv $@.tmp $@ > +OVN_BR_PIC = ovn-br.pic > +OVN_BR_DOT_DIAGRAM_ARG = --er-diagram=$(OVN_BR_PIC) > +CLEANFILES += ovn-br.gv ovn-br.pic > +endif > + > +# OVN bridge controller schema documentation > +EXTRA_DIST += ovn-br.xml > +CLEANFILES += ovn-br.5 > +man_MANS += ovn-br.5 > + > +ovn-br.5: \ > + ${OVSDIR}/ovsdb/ovsdb-doc $(srcdir)/ovn-br.xml $(srcdir)/ovn-br.ovsschema $(OVN_BR_PIC) > + $(AM_V_GEN)$(OVSDB_DOC) \ > + $(OVN_BR_DOT_DIAGRAM_ARG) \ > + --version=$(VERSION) \ > + $(srcdir)/ovn-br.ovsschema \ > + $(srcdir)/ovn-br.xml > $@.tmp && \ > + mv $@.tmp $@ > + > # Version checking for ovn-nb.ovsschema. > ALL_LOCAL += ovn-nb.ovsschema.stamp > ovn-nb.ovsschema.stamp: ovn-nb.ovsschema > @@ -143,9 +172,16 @@ ovn-ic-sb.ovsschema.stamp: ovn-ic-sb.ovsschema > $(srcdir)/build-aux/cksum-schema-check $? $@ > CLEANFILES += ovn-ic-sb.ovsschema.stamp > > +# Version checking for ovn-br.ovsschema. > +ALL_LOCAL += ovn-br.ovsschema.stamp > +ovn-br.ovsschema.stamp: ovn-br.ovsschema > + $(srcdir)/build-aux/cksum-schema-check $? $@ > +CLEANFILES += ovn-br.ovsschema.stamp > + > pkgdata_DATA += ovn-nb.ovsschema > pkgdata_DATA += ovn-sb.ovsschema > pkgdata_DATA += ovn-ic-nb.ovsschema > pkgdata_DATA += ovn-ic-sb.ovsschema > +pkgdata_DATA += ovn-br.ovsschema > > CLEANFILES += ovn-sb.ovsschema.stamp > diff --git a/br-controller/automake.mk b/br-controller/automake.mk > new file mode 100644 > index 0000000000..012e9a5ed8 > --- /dev/null > +++ b/br-controller/automake.mk > @@ -0,0 +1,8 @@ > +bin_PROGRAMS += br-controller/ovn-br-controller > +br_controller_ovn_br_controller_SOURCES = \ > + br-controller/ovn-br-controller.c > + > +br_controller_ovn_br_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la > +man_MANS += br-controller/ovn-br-controller.8 > +EXTRA_DIST += br-controller/ovn-br-controller.8.xml > +CLEANFILES += br-controller/ovn-br-controller.8 > diff --git a/br-controller/ovn-br-controller.8.xml b/br-controller/ovn-br-controller.8.xml > new file mode 100644 > index 0000000000..fa6eb2e2e2 > --- /dev/null > +++ b/br-controller/ovn-br-controller.8.xml > @@ -0,0 +1,5 @@ > +<?xml version="1.0" encoding="utf-8"?> > +<manpage program="ovn-br-controller" section="8" title="ovn-br-controller"> > + <h1>Name</h1> > + <p>ovn-br-controller -- Open Virtual Network local OVS bridge controller</p> > +</manpage> > diff --git a/br-controller/ovn-br-controller.c b/br-controller/ovn-br-controller.c > new file mode 100644 > index 0000000000..0fef0e5fee > --- /dev/null > +++ b/br-controller/ovn-br-controller.c > @@ -0,0 +1,175 @@ > +/* Copyright (c) 2025 Crusoe Energy Systems LLC > + * > + * Licensed under the Apache License, Version 2.0 (the "License"); > + * you may not use this file except in compliance with the License. > + * You may obtain a copy of the License at: > + * > + * http://www.apache.org/licenses/LICENSE-2.0 > + * > + * Unless required by applicable law or agreed to in writing, software > + * distributed under the License is distributed on an "AS IS" BASIS, > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. > + * See the License for the specific language governing permissions and > + * limitations under the License. > + */ > + > +#include <config.h> > + > +#include <errno.h> > +#include <getopt.h> > +#include <signal.h> > +#include <stdlib.h> > +#include <string.h> > + > +/* OVS includes. */ > +#include "openvswitch/vlog.h" > +#include "lib/command-line.h" > +#include "lib/daemon.h" > +#include "lib/dirs.h" > +#include "lib/fatal-signal.h" > +#include "lib/stream.h" > +#include "lib/stream-ssl.h" > +#include "lib/unixctl.h" > + > +/* OVN includes. */ > +#include "lib/ovn-br-idl.h" > +#include "lib/ovn-util.h" > + > +VLOG_DEFINE_THIS_MODULE(main); > + > +static void parse_options(int argc, char *argv[]); > +OVS_NO_RETURN static void usage(void); > + > + > +/* SSL/TLS options. */ > +static const char *ssl_private_key_file; > +static const char *ssl_certificate_file; > +static const char *ssl_ca_cert_file; > + > +/* --unixctl-path: Path to use for unixctl server socket. */ > +static char *unixctl_path; > + > +int > +main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) > +{ > + ovs_cmdl_proctitle_init(argc, argv); > + ovn_set_program_name(argv[0]); > + service_start(&argc, &argv); > + parse_options(argc, argv); > + fatal_ignore_sigpipe(); > + > + return 0; > +} > + > +/* static functions. */ > +static void > +parse_options(int argc, char *argv[]) > +{ > + enum { > + OPT_PEER_CA_CERT = UCHAR_MAX + 1, > + OPT_BOOTSTRAP_CA_CERT, > + VLOG_OPTION_ENUMS, > + OVN_DAEMON_OPTION_ENUMS, > + SSL_OPTION_ENUMS, > + }; > + > + static struct option long_options[] = { > + {"help", no_argument, NULL, 'h'}, > + {"version", no_argument, NULL, 'V'}, > + {"unixctl", required_argument, NULL, 'u'}, > + VLOG_LONG_OPTIONS, > + OVN_DAEMON_LONG_OPTIONS, > + STREAM_SSL_LONG_OPTIONS, > + {"peer-ca-cert", required_argument, NULL, OPT_PEER_CA_CERT}, > + {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT}, > + {NULL, 0, NULL, 0} > + }; > + char *short_options = ovs_cmdl_long_options_to_short_options(long_options); > + > + for (;;) { > + int c; > + > + c = getopt_long(argc, argv, short_options, long_options, NULL); > + if (c == -1) { > + break; > + } > + > + switch (c) { > + case 'h': > + usage(); > + > + case 'V': > + ovs_print_version(OFP15_VERSION, OFP15_VERSION); > + printf("OVN BR DB Schema %s\n", ovnbrrec_get_db_version()); > + exit(EXIT_SUCCESS); > + > + case 'u': > + unixctl_path = optarg; > + break; > + > + VLOG_OPTION_HANDLERS > + OVN_DAEMON_OPTION_HANDLERS > + > + case 'p': > + ssl_private_key_file = optarg; > + break; > + > + case 'c': > + ssl_certificate_file = optarg; > + break; > + > + case 'C': > + ssl_ca_cert_file = optarg; > + break; > + > + case OPT_SSL_PROTOCOLS: > + stream_ssl_set_protocols(optarg); > + break; > + > + case OPT_SSL_CIPHERS: > + stream_ssl_set_ciphers(optarg); > + break; > + > + case OPT_SSL_CIPHERSUITES: > + stream_ssl_set_ciphersuites(optarg); > + break; > + > + case OPT_PEER_CA_CERT: > + stream_ssl_set_peer_ca_cert_file(optarg); > + break; > + > + case OPT_BOOTSTRAP_CA_CERT: > + stream_ssl_set_ca_cert_file(optarg, true); > + break; > + > + case '?': > + exit(EXIT_FAILURE); > + > + default: > + ovs_abort(0, "Invalid option."); > + } > + } > + free(short_options); > + > + argc -= optind; > + argv += optind; > +} > + > +static void > +usage(void) > +{ > + printf("%s: OVN bridge controller\n" > + "usage %s [OPTIONS] [OVS-DATABASE]\n" > + "where OVS-DATABASE is a socket on which the OVS OVSDB server " > + "is listening.\n", > + program_name, program_name); > + stream_usage("OVS-DATABASE", true, false, true); > + daemon_usage(); > + vlog_usage(); > + printf("\nOther options:\n" > + " -u, --unixctl=SOCKET set control socket name\n" > + " -n custom chassis name\n" > + " -h, --help display this help message\n" > + " -V, --version display version information\n"); > + exit(EXIT_SUCCESS); > +} > diff --git a/lib/automake.mk b/lib/automake.mk > index a59c722d62..46c24e99d1 100644 > --- a/lib/automake.mk > +++ b/lib/automake.mk > @@ -59,7 +59,9 @@ nodist_lib_libovn_la_SOURCES = \ > lib/ovn-ic-nb-idl.c \ > lib/ovn-ic-nb-idl.h \ > lib/ovn-ic-sb-idl.c \ > - lib/ovn-ic-sb-idl.h > + lib/ovn-ic-sb-idl.h \ > + lib/ovn-br-idl.c \ > + lib/ovn-br-idl.h > > CLEANFILES += $(nodist_lib_libovn_la_SOURCES) > > @@ -129,3 +131,16 @@ OVN_IC_SB_IDL_FILES = \ > lib/ovn-ic-sb-idl.ovsidl: $(OVN_IC_SB_IDL_FILES) > $(AM_V_GEN)$(OVSDB_IDLC) annotate $(OVN_IC_SB_IDL_FILES) > $@.tmp && \ > mv $@.tmp $@ > + > +# ovn-br IDL > +OVSIDL_BUILT += \ > + lib/ovn-br-idl.c \ > + lib/ovn-br-idl.h \ > + lib/ovn-br-idl.ovsidl > +EXTRA_DIST += lib/ovn-br-idl.ann > +OVN_PR_IDL_FILES = \ > + $(srcdir)/ovn-br.ovsschema \ > + $(srcdir)/lib/ovn-br-idl.ann > +lib/ovn-br-idl.ovsidl: $(OVN_PR_IDL_FILES) > + $(AM_V_GEN)$(OVSDB_IDLC) annotate $(OVN_PR_IDL_FILES) > $@.tmp && \ > + mv $@.tmp $@ > diff --git a/lib/ovn-br-idl.ann b/lib/ovn-br-idl.ann > new file mode 100644 > index 0000000000..80993338a6 > --- /dev/null > +++ b/lib/ovn-br-idl.ann > @@ -0,0 +1,9 @@ > +# -*- python -*- > + > +# This code, when invoked by "ovsdb-idlc annotate" (by the build > +# process), annotates ovn-br.ovsschema with additional data that give > +# the ovsdb-idl engine information about the types involved, so that > +# it can generate more programmer-friendly data structures. > + > +s["idlPrefix"] = "ovnbrrec_" > +s["idlHeader"] = "\"lib/ovn-br-idl.h\"" > diff --git a/lib/ovn-util.c b/lib/ovn-util.c > index ab5c4fc477..1cfeed6044 100644 > --- a/lib/ovn-util.c > +++ b/lib/ovn-util.c > @@ -560,6 +560,19 @@ default_ic_sb_db(void) > return def; > } > > +const char * > +default_br_db(void) > +{ > + static char *def; > + if (!def) { > + def = getenv("OVN_BR_DB"); > + if (!def) { > + def = xasprintf("unix:%s/ovnbr_db.sock", ovn_rundir()); > + } > + } > + return def; > +} > + > char * > get_abs_unix_ctl_path(const char *path) > { > diff --git a/lib/ovn-util.h b/lib/ovn-util.h > index bde40666a1..c66d7c31ff 100644 > --- a/lib/ovn-util.h > +++ b/lib/ovn-util.h > @@ -131,6 +131,7 @@ const char *default_nb_db(void); > const char *default_sb_db(void); > const char *default_ic_nb_db(void); > const char *default_ic_sb_db(void); > +const char *default_br_db(void); > char *get_abs_unix_ctl_path(const char *path); > > struct ovsdb_idl_table_class; > diff --git a/ovn-br.ovsschema b/ovn-br.ovsschema > new file mode 100644 > index 0000000000..5635ee5bbd > --- /dev/null > +++ b/ovn-br.ovsschema > @@ -0,0 +1,94 @@ > +{ > + "name": "OVN_Bridge_Controller", > + "version": "0.0.1", > + "cksum": "124113656 4389", > + "tables": { > + "BR_Global": { > + "columns": { > + "br_cfg": {"type": {"key": "integer"}}, > + "external_ids": { > + "type": {"key": "string", "value": "string", > + "min": 0, "max": "unlimited"}}, > + "connections": { > + "type": {"key": {"type": "uuid", > + "refTable": "Connection"}, > + "min": 0, > + "max": "unlimited"}}, > + "ssl": { > + "type": {"key": {"type": "uuid", > + "refTable": "SSL"}, > + "min": 0, "max": 1}}, > + "options": { > + "type": {"key": "string", "value": "string", > + "min": 0, "max": "unlimited"}}}, > + "maxRows": 1, > + "isRoot": true}, > + "Connection": { > + "columns": { > + "target": {"type": "string"}, > + "max_backoff": {"type": {"key": {"type": "integer", > + "minInteger": 1000}, > + "min": 0, > + "max": 1}}, > + "inactivity_probe": {"type": {"key": "integer", > + "min": 0, > + "max": 1}}, > + "other_config": {"type": {"key": "string", > + "value": "string", > + "min": 0, > + "max": "unlimited"}}, > + "external_ids": {"type": {"key": "string", > + "value": "string", > + "min": 0, > + "max": "unlimited"}}, > + "is_connected": {"type": "boolean", "ephemeral": true}, > + "status": {"type": {"key": "string", > + "value": "string", > + "min": 0, > + "max": "unlimited"}, > + "ephemeral": true}}, > + "indexes": [["target"]]}, > + "SSL": { > + "columns": { > + "private_key": {"type": "string"}, > + "certificate": {"type": "string"}, > + "ca_cert": {"type": "string"}, > + "bootstrap_ca_cert": {"type": "boolean"}, > + "ssl_protocols": {"type": "string"}, > + "ssl_ciphers": {"type": "string"}, > + "ssl_ciphersuites": {"type": "string"}, > + "external_ids": {"type": {"key": "string", > + "value": "string", > + "min": 0, > + "max": "unlimited"}}}, > + "maxRows": 1}, > + "Bridge": { > + "columns": { > + "name": {"type": "string"}, > + "options": { > + "type": {"key": "string", "value": "string", > + "min": 0, "max": "unlimited"}}, > + "external_ids": { > + "type": {"key": "string", "value": "string", > + "min": 0, "max": "unlimited"}}}, > + "indexes": [["name"]], > + "isRoot": true}, > + "Logical_Flow": { > + "columns": { > + "bridge": {"type": {"key": {"type": "uuid", > + "refTable": "Bridge"}, > + "min": 0, "max": 1}}, Out of curiosity, what happens if you run ovn-brcontroller on the same hypervisor where ovn-controller is running, then you create a Bridge in the BR DB for "br-int" and you add logical flows to the BR DB for "br-int"? Should there be safeguards in the code to prevent this? > + "table_id": {"type": {"key": {"type": "integer", > + "minInteger": 0, > + "maxInteger": 32}}}, Why is the table_id limited to 32? We don't have ingress and egress pipelines, so the table_id should have a larger range for these logical flows. > + "priority": {"type": {"key": {"type": "integer", > + "minInteger": 0, > + "maxInteger": 65535}}}, > + "match": {"type": "string"}, > + "actions": {"type": "string"}, > + "external_ids": { > + "type": {"key": "string", "value": "string", > + "min": 0, "max": "unlimited"}}}, > + "isRoot": true} > + } > +} > diff --git a/ovn-br.xml b/ovn-br.xml > new file mode 100644 > index 0000000000..be6232094b > --- /dev/null > +++ b/ovn-br.xml > @@ -0,0 +1,452 @@ > +<?xml version="1.0" encoding="utf-8"?> > +<database name="ovn-bridge-controller" title="OVN Bridge Controller Database"> > + <p> > + This database is the interface between OVN Bridge Controller and the > + cloud management system (CMS) to program and control the OVS bridges > + using OVN logical flows. The CMS produces almost all of > + the contents of the database. The <code>ovn-bridge-controller</code> program > + monitors the database contents and programs the OVS bridges with the > + OpenFlow rules. > + </p> > + > + <h2>External IDs</h2> > + > + <p> > + Each of the tables in this database contains a special column, named > + <code>external_ids</code>. This column has the same form and purpose each > + place it appears. > + </p> > + > + <dl> > + <dt><code>external_ids</code>: map of string-string pairs</dt> > + <dd> > + Key-value pairs for use by the CMS. The CMS might use certain pairs, for > + example, to identify entities in its own configuration that correspond to > + those in this database. > + </dd> > + </dl> > + > + <table name="BR_Global" title="Bridge Controller configuration"> > + <column name="br_cfg"> > + Sequence number for client to increment. When a client modifies any > + part of the bridge Controller database configuration and wishes to wait > + for <code>ovn-br-controller</code> to finish applying the changes, it may > + increment this sequence number. > + </column> > + > + <group title="Common Columns"> > + <column name="external_ids"> > + See <em>External IDs</em> at the beginning of this document. > + </column> > + </group> > + > + <group title="Common options"> > + <column name="options"> > + This column provides general key/value settings. The supported > + options are described individually below. > + </column> > + </group> > + > + <group title="Connection Options"> > + <column name="connections"> > + Database clients to which the Open vSwitch database server should > + connect or on which it should listen, along with options for how these > + connections should be configured. See the <ref table="Connection"/> > + table for more information. > + </column> > + <column name="ssl"> > + Global SSL/TLS configuration. > + </column> > + </group> > + </table> > + > + <table name="Connection" title="OVSDB client connections."> > + <p> > + Configuration for a database connection to an Open vSwitch database > + (OVSDB) client. > + </p> > + > + <p> > + This table primarily configures the Open vSwitch database server > + (<code>ovsdb-server</code>). > + </p> > + > + <p> > + The Open vSwitch database server can initiate and maintain active > + connections to remote clients. It can also listen for database > + connections. > + </p> > + > + <group title="Core Features"> > + <column name="target"> > + <p>Connection methods for clients.</p> > + <p> > + The following connection methods are currently supported: > + </p> > + <dl> > + <dt><code>ssl:<var>host</var></code>[<code>:<var>port</var></code>]</dt> > + <dd> > + <p> > + The specified SSL/TLS <var>port</var> on the host at the given > + <var>host</var>, which can either be a DNS name (if built with > + unbound library) or an IP address. A valid SSL/TLS configuration > + must be provided when this form is used, this configuration can > + be specified via command-line options or the <ref table="SSL"/> > + table. > + </p> > + <p> > + If <var>port</var> is not specified, it defaults to 6640. > + </p> > + <p> > + SSL/TLS support is an optional feature that is not always > + built as part of OVN or Open vSwitch. > + </p> > + </dd> > + > + <dt><code>tcp:<var>host</var></code>[<code>:<var>port</var></code>]</dt> > + <dd> > + <p> > + The specified TCP <var>port</var> on the host at the given > + <var>host</var>, which can either be a DNS name (if built with > + unbound library) or an IP address. If <var>host</var> is an IPv6 > + address, wrap it in square brackets, e.g. <code>tcp:[::1]:6640</code>. > + </p> > + <p> > + If <var>port</var> is not specified, it defaults to 6640. > + </p> > + </dd> > + <dt><code>pssl:</code>[<var>port</var>][<code>:<var>host</var></code>]</dt> > + <dd> > + <p> > + Listens for SSL/TLS connections on the specified TCP > + <var>port</var>. > + Specify 0 for <var>port</var> to have the kernel automatically > + choose an available port. If <var>host</var>, which can either > + be a DNS name (if built with unbound library) or an IP address, > + is specified, then connections are restricted to the resolved or > + specified local IPaddress (either IPv4 or IPv6 address). If > + <var>host</var> is an IPv6 address, wrap in square brackets, > + e.g. <code>pssl:6640:[::1]</code>. If <var>host</var> is not > + specified then it listens only on IPv4 (but not IPv6) addresses. > + A valid SSL/TLS configuration must be provided when this form is > + used, this can be specified either via command-line options or > + the <ref table="SSL"/> table. > + </p> > + <p> > + If <var>port</var> is not specified, it defaults to 6640. > + </p> > + <p> > + SSL/TLS support is an optional feature that is not always built > + as part of OVN or Open vSwitch. > + </p> > + </dd> > + <dt><code>ptcp:</code>[<var>port</var>][<code>:<var>host</var></code>]</dt> > + <dd> > + <p> > + Listens for connections on the specified TCP <var>port</var>. > + Specify 0 for <var>port</var> to have the kernel automatically > + choose an available port. If <var>host</var>, which can either > + be a DNS name (if built with unbound library) or an IP address, > + is specified, then connections are restricted to the resolved or > + specified local IP address (either IPv4 or IPv6 address). If > + <var>host</var> is an IPv6 address, wrap it in square brackets, > + e.g. <code>ptcp:6640:[::1]</code>. If <var>host</var> is not > + specified then it listens only on IPv4 addresses. > + </p> > + <p> > + If <var>port</var> is not specified, it defaults to 6640. > + </p> > + </dd> > + </dl> > + <p>When multiple clients are configured, the <ref column="target"/> > + values must be unique. Duplicate <ref column="target"/> values yield > + unspecified results.</p> > + </column> > + </group> > + > + <group title="Client Failure Detection and Handling"> > + <column name="max_backoff"> > + Maximum number of milliseconds to wait between connection attempts. > + Default is implementation-specific. > + </column> > + > + <column name="inactivity_probe"> > + Maximum number of milliseconds of idle time on connection to the client > + before sending an inactivity probe message. If Open vSwitch does not > + communicate with the client for the specified number of seconds, it > + will send a probe. If a response is not received for the same > + additional amount of time, Open vSwitch assumes the connection has been > + broken and attempts to reconnect. Default is implementation-specific. > + A value of 0 disables inactivity probes. > + </column> > + </group> > + > + <group title="Status"> > + <p> > + Key-value pair of <ref column="is_connected"/> is always updated. > + Other key-value pairs in the status columns may be updated depends > + on the <ref column="target"/> type. > + </p> > + > + <p> > + When <ref column="target"/> specifies a connection method that > + listens for inbound connections (e.g. <code>ptcp:</code> or > + <code>punix:</code>), both <ref column="n_connections"/> and > + <ref column="is_connected"/> may also be updated while the > + remaining key-value pairs are omitted. > + </p> > + > + <p> > + On the other hand, when <ref column="target"/> specifies an > + outbound connection, all key-value pairs may be updated, except > + the above-mentioned two key-value pairs associated with inbound > + connection targets. They are omitted. > + </p> > + > + <column name="is_connected"> > + <code>true</code> if currently connected to this client, > + <code>false</code> otherwise. > + </column> > + > + <column name="status" key="last_error"> > + A human-readable description of the last error on the connection > + to the manager; i.e. <code>strerror(errno)</code>. This key > + will exist only if an error has occurred. > + </column> > + > + <column name="status" key="state" > + type='{"type": "string", "enum": ["set", ["VOID", "BACKOFF", "CONNECTING", "ACTIVE", "IDLE"]]}'> > + <p> > + The state of the connection to the manager: > + </p> > + <dl> > + <dt><code>VOID</code></dt> > + <dd>Connection is disabled.</dd> > + > + <dt><code>BACKOFF</code></dt> > + <dd>Attempting to reconnect at an increasing period.</dd> > + > + <dt><code>CONNECTING</code></dt> > + <dd>Attempting to connect.</dd> > + > + <dt><code>ACTIVE</code></dt> > + <dd>Connected, remote host responsive.</dd> > + > + <dt><code>IDLE</code></dt> > + <dd>Connection is idle. Waiting for response to keep-alive.</dd> > + </dl> > + <p> > + These values may change in the future. They are provided only for > + human consumption. > + </p> > + </column> > + > + <column name="status" key="sec_since_connect" > + type='{"type": "integer", "minInteger": 0}'> > + The amount of time since this client last successfully connected > + to the database (in seconds). Value is empty if client has never > + successfully been connected. > + </column> > + > + <column name="status" key="sec_since_disconnect" > + type='{"type": "integer", "minInteger": 0}'> > + The amount of time since this client last disconnected from the > + database (in seconds). Value is empty if client has never > + disconnected. > + </column> > + > + <column name="status" key="locks_held"> > + Space-separated list of the names of OVSDB locks that the connection > + holds. Omitted if the connection does not hold any locks. > + </column> > + > + <column name="status" key="locks_waiting"> > + Space-separated list of the names of OVSDB locks that the connection is > + currently waiting to acquire. Omitted if the connection is not waiting > + for any locks. > + </column> > + > + <column name="status" key="locks_lost"> > + Space-separated list of the names of OVSDB locks that the connection > + has had stolen by another OVSDB client. Omitted if no locks have been > + stolen from this connection. > + </column> > + > + <column name="status" key="n_connections" > + type='{"type": "integer", "minInteger": 2}'> > + When <ref column="target"/> specifies a connection method that > + listens for inbound connections (e.g. <code>ptcp:</code> or > + <code>pssl:</code>) and more than one connection is actually active, > + the value is the number of active connections. Otherwise, this > + key-value pair is omitted. > + </column> > + > + <column name="status" key="bound_port" type='{"type": "integer"}'> > + When <ref column="target"/> is <code>ptcp:</code> or > + <code>pssl:</code>, this is the TCP port on which the OVSDB server is > + listening. (This is particularly useful when <ref > + column="target"/> specifies a port of 0, allowing the kernel to > + choose any available port.) > + </column> > + </group> > + > + <group title="Common Columns"> > + The overall purpose of these columns is described under <code>Common > + Columns</code> at the beginning of this document. > + > + <column name="external_ids"/> > + <column name="other_config"/> > + </group> > + </table> > + > + <table name="SSL"> > + SSL/TLS configuration for ovn-nb database access. > + > + <column name="private_key"> > + Name of a PEM file containing the private key used as the switch's > + identity for SSL/TLS connections to the controller. > + </column> > + > + <column name="certificate"> > + Name of a PEM file containing a certificate, signed by the > + certificate authority (CA) used by the controller and manager, > + that certifies the switch's private key, identifying a trustworthy > + switch. > + </column> > + > + <column name="ca_cert"> > + Name of a PEM file containing the CA certificate used to verify > + that the switch is connected to a trustworthy controller. > + </column> > + > + <column name="bootstrap_ca_cert"> > + If set to <code>true</code>, then Open vSwitch will attempt to > + obtain the CA certificate from the controller on its first SSL/TLS > + 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/TLS connections must be authenticated by a certificate signed > + by the CA certificate thus obtained. <em>This option exposes the > + SSL/TLS connection to a man-in-the-middle attack obtaining the initial > + CA certificate.</em> It may still be useful for bootstrapping. > + </column> > + > + <column name="ssl_protocols"> > + <p> > + Range or a comma- or space-delimited list of the SSL/TLS protocols to > + enable for SSL/TLS connections. > + </p> > + <p> > + Supported protocols include <code>TLSv1.2</code> and > + <code>TLSv1.3</code>. Ranges can be provided in a form of two protocol > + names separated with a dash (<code>TLSv1.2-TLSv1.3</code>), or as a > + single protocol name with a plus sign (<code>TLSv1.2+</code>). The > + value can be a list of protocols or exactly one range. The range is a > + preferred way of specifying protocols and the configuration always > + behaves as if the range between the minimum and the maximum specified > + version is provided, i.e., if the value is set to > + <code>TLSv1.X,TLSv1.(X+2)</code>, the <code>TLSv1.(X+1)</code> will > + also be enabled as if it was a range. > + Regardless of order, the highest protocol supported by both sides will > + be chosen when making the connection. > + </p> > + <p> > + The default when this option is omitted is <code>TLSv1.2+</code>. > + </p> > + </column> > + > + <column name="ssl_ciphers"> > + List of ciphers (in OpenSSL cipher string format) to be supported > + for SSL/TLS connections with TLSv1.2. The default when this option > + is omitted is <code>DEFAULT:@SECLEVEL=2</code>. > + </column> > + > + <column name="ssl_ciphersuites"> > + List of ciphersuites (in OpenSSL ciphersuites string format) to be > + supported for SSL/TLS connections with TLSv1.3 and later. Default value > + from OpenSSL will be used when this option is omitted. > + </column> > + > + <group title="Common Columns"> > + The overall purpose of these columns is described under <code>Common > + Columns</code> at the beginning of this document. > + > + <column name="external_ids"/> > + </group> > + </table> > + > + <table name="Bridge" title="OVS Bridge to control"> > + <column name="name"> > + Name of the OVS bridge. This bridge should exist in the local OVS > + database. > + </column> > + > + <column name="options"> > + Reserved for future use. > + </column> > + > + <column name="external_ids"> > + See <em>External IDs</em> at the beginning of this document. > + </column> > + </table> > + > + <table name="Logical_Flow" title="Logical flow"> > + <column name="bridge"> > + The bridge to which the logical flow belongs to. > + </column> > + > + <column name="table_id"> > + The stage in the logical pipeline, analogous to an OpenFlow table number. > + </column> > + > + <column name="priority"> > + The flow's priority. Flows with numerically higher priority take > + precedence over those with lower. If two logical flows with the > + same priority both match, then the one actually applied to the packet is > + undefined. > + </column> > + > + <column name="match"> > + <p> > + A matching expression. OVN provides a superset of OpenFlow matching > + capabilities, using a syntax similar to Boolean expressions in a > + programming language. > + </p> > + > + <p> > + Please see the documentation of the > + <ref > + column="match" table="Logical_Flow" db="OVN_Southbound"/> > + column of the <ref table="Logical_Flow" > + db="OVN_Southbound"/> table in the <ref db="OVN_Southbound"/> database. > + </p> > + </column> > + > + <column name="actions"> > + <p> > + Logical datapath actions, to be executed when the logical flow > + represented by this row is the highest-priority match. > + </p> > + > + <p> > + Actions share lexical syntax with the <ref column="match"/> column. An > + empty set of actions (or one that contains just white space or > + comments), or a set of actions that consists of just > + <code>drop;</code>, causes the matched packets to be dropped. > + Otherwise, the column should contain a sequence of actions, each > + terminated by a semicolon. > + </p> > + > + <p> > + Please see the documentation of the > + <ref > + column="actions" table="Logical_Flow" db="OVN_Southbound"/> > + column of the <ref table="Logical_Flow" > + db="OVN_Southbound"/> table in the <ref db="OVN_Southbound"/> database. > + </p> > + </column> > + > + <column name="external_ids"> > + See <em>External IDs</em> at the beginning of this document. > + </column> > + </table> > +</database> > diff --git a/tutorial/ovn-sandbox b/tutorial/ovn-sandbox > index ed334d1c31..239db58f35 100755 > --- a/tutorial/ovn-sandbox > +++ b/tutorial/ovn-sandbox > @@ -64,6 +64,8 @@ gdb_ovn_controller=false > gdb_ovn_controller_ex=false > gdb_ovn_controller_vtep=false > gdb_ovn_controller_vtep_ex=false > +gdb_ovn_br_controller=false > +gdb_ovn_br_controller_ex=false > builddir= > ovsbuilddir= > srcdir= > @@ -90,6 +92,7 @@ ic_nb_servers=3 > ic_sb_model=clustered > ic_sb_servers=3 > dummy=override > +ovn_br=false > > for option; do > # This option-parsing mechanism borrowed from a Autoconf-generated > @@ -339,6 +342,9 @@ EOF > --ic-sb-m*) > prev=ic_sb_model > ;; > + --ovn-br) > + ovn_br=true > + ;; > -R|--gdb-run) > gdb_vswitchd_ex=true > gdb_ovsdb_ex=true > @@ -422,6 +428,11 @@ if $built; then > echo >&2 'source directory not found, please use --srcdir' > exit 1 > fi > + br_schema=$srcdir/ovn-br.ovsschema > + if test ! -e "$br_schema"; then > + echo >&2 'source directory not found, please use --srcdir' > + exit 1 > + fi > > # Put built tools early in $PATH. > if test ! -e $ovsbuilddir/vswitchd/ovs-vswitchd; then > @@ -429,7 +440,7 @@ if $built; then > exit 1 > fi > PATH=$ovsbuilddir/ovsdb:$ovsbuilddir/vswitchd:$ovsbuilddir/utilities:$ovsbuilddir/vtep:$PATH > - PATH=$builddir/controller:$builddir/controller-vtep:$builddir/northd:$builddir/ic:$builddir/utilities:$PATH > + PATH=$builddir/controller:$builddir/controller-vtep:$builddir/northd:$builddir/ic:$builddir/utilities:$builddir/br-controller:$PATH > export PATH > else > case $schema in > @@ -616,6 +627,10 @@ ovn_start_db sb "$sbdb_model" "$sbdb_servers" "$ovnsb_source" > ovn_start_db ic_nb "$ic_nb_model" "$ic_nb_servers" "$ic_nb_schema" > ovn_start_db ic_sb "$ic_sb_model" "$ic_sb_servers" "$ic_sb_schema" > > +if $ovn_br; then > + ovn_start_db br standalone 1 "$br_schema" > +fi > + > #Add a small delay to allow ovsdb-server to launch. > sleep 0.1 > > @@ -692,6 +707,13 @@ rungdb $gdb_ovn_controller_vtep $gdb_ovn_controller_vtep_ex \ > $OVN_CTRLR_PKI --log-file -vsyslog:off \ > --ovnsb-db="$OVN_SB_DB" > > +if $ovn_br; then > + run ovs-vsctl set open . external-ids:ovn-br-remote=$OVN_BR_DB > + rungdb $gdb_ovn_br_controller $gdb_ovn_br_controller_ex ovn-br-controller \ > + --detach --no-chdir -vsyslog:off \ > + --log-file=ovn-br-controller.log \ > + --pidfile=ovn-br-controller.pid -vconsole:off > +fi > cat <<EOF > > > diff --git a/utilities/automake.mk b/utilities/automake.mk > index 1de33614f9..3e3f2e08c9 100644 > --- a/utilities/automake.mk > +++ b/utilities/automake.mk > @@ -119,4 +119,12 @@ bin_PROGRAMS += utilities/ovn-debug > utilities_ovn_debug_SOURCES = utilities/ovn-debug.c > utilities_ovn_debug_LDADD = lib/libovn.la $(OVSDB_LIBDIR)/libovsdb.la $(OVS_LIBDIR)/libopenvswitch.la > > +# ovn-brctl > +bin_PROGRAMS += utilities/ovn-brctl > +utilities_ovn_brctl_SOURCES = \ > + utilities/ovn-dbctl.c \ > + utilities/ovn-dbctl.h \ > + utilities/ovn-brctl.c > +utilities_ovn_brctl_LDADD = lib/libovn.la $(OVSDB_LIBDIR)/libovsdb.la $(OVS_LIBDIR)/libopenvswitch.la > + > include utilities/bugtool/automake.mk > diff --git a/utilities/ovn-brctl.c b/utilities/ovn-brctl.c > new file mode 100644 > index 0000000000..051a897748 > --- /dev/null > +++ b/utilities/ovn-brctl.c > @@ -0,0 +1,585 @@ > +/* > + * Licensed under the Apache License, Version 2.0 (the "License"); > + * you may not use this file except in compliance with the License. > + * You may obtain a copy of the License at: > + * > + * http://www.apache.org/licenses/LICENSE-2.0 > + * > + * Unless required by applicable law or agreed to in writing, software > + * distributed under the License is distributed on an "AS IS" BASIS, > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. > + * See the License for the specific language governing permissions and > + * limitations under the License. > + */ > + > + #include <config.h> > + > +#include <getopt.h> > +#include <inttypes.h> > +#include <stdlib.h> > +#include <stdio.h> > + > +/* OVS includes. */ > +#include "command-line.h" > +#include "daemon.h" > +#include "db-ctl-base.h" > +#include "dirs.h" > +#include "openvswitch/vlog.h" > + > +/* OVN includes. */ > +#include "lib/ovn-br-idl.h" > +#include "lib/ovn-dirs.h" > +#include "lib/ovn-util.h" > +#include "lib/vec.h" > +#include "ovn-dbctl.h" > + > +VLOG_DEFINE_THIS_MODULE(brctl); > + > +static char * OVS_WARN_UNUSED_RESULT br_by_name_or_uuid( > + struct ctl_context *ctx, const char *id, bool must_exist, > + const struct ovnbrrec_bridge **br_p); > + > +static void > +brctl_add_base_prerequisites(struct ovsdb_idl *idl, > + enum nbctl_wait_type wait_type OVS_UNUSED) > +{ > + ovsdb_idl_add_table(idl, &ovnbrrec_table_br_global); > +} > + > +static void > +brctl_pre_execute(struct ovsdb_idl *idl, struct ovsdb_idl_txn *txn, > + enum nbctl_wait_type wait_type OVS_UNUSED) > +{ > + const struct ovnbrrec_br_global *br = ovnbrrec_br_global_first(idl); > + if (!br) { > + br = ovnbrrec_br_global_insert(txn); > + } > +} > + > +static int > +get_inactivity_probe(struct ovsdb_idl *idl) > +{ > + const struct ovnbrrec_br_global *pr = ovnbrrec_br_global_first(idl); > + int interval = DEFAULT_UTILS_PROBE_INTERVAL_MSEC; > + > + if (pr) { > + interval = smap_get_int(&pr->options, "brctl_probe_interval", > + interval); > + } > + > + return interval; > +} > + > +/* ovn-brctl specific context. Inherits the 'struct ctl_context' as base. */ > +struct brctl_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 The comment references sbctl_ functions instead of brctl_ functions. However, neither sbctl_context_populate_cache() nor brctl_context_populate_cache() actually exist. In the case of ovn-sbctl, it maintains a cache of some objects, but ovn-brctl is not doing that. I think that you can remove the cache_valid field altogether. > + * maintain its correctness. */ > + bool cache_valid; > +}; > + > +/* Casts 'base' into 'struct sbctl_context'. */ > +static struct brctl_context * > +brctl_context_cast(struct ctl_context *base) > +{ > + return CONTAINER_OF(base, struct brctl_context, base); > +} > + > +static struct ctl_context * > +brctl_ctx_create(void) > +{ > + struct brctl_context *prctx = xmalloc(sizeof *prctx); > + *prctx = (struct brctl_context) { > + .cache_valid = false, > + }; > + return &prctx->base; > +} > + > +static void > +brctl_context_invalidate_cache(struct ctl_context *ctx) > +{ > + struct brctl_context *brctl_ctx = brctl_context_cast(ctx); > + > + if (!brctl_ctx->cache_valid) { > + return; > + } > + brctl_ctx->cache_valid = false; > +} > + > +static void > +brctl_ctx_destroy(struct ctl_context *ctx) > +{ > + brctl_context_invalidate_cache(ctx); > + free(ctx); > +} > + > +static void > +print_br(const struct ovnbrrec_bridge *br, struct ds *s) > +{ > + ds_put_format(s, "bridge "UUID_FMT" (%s)\n", > + UUID_ARGS(&br->header_.uuid), br->name); > +} > + > +static void > +brctl_init(struct ctl_context *ctx OVS_UNUSED) > +{ > +} > + > +static void > +brctl_pre_show(struct ctl_context *ctx) > +{ > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_name); > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_options); > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_external_ids); > +} > + > +static const struct ctl_table_class tables[OVNBRREC_N_TABLES] = { > + [OVNBRREC_TABLE_BRIDGE].row_ids[0] > + = {&ovnbrrec_bridge_col_name, NULL, NULL}, > + > + [OVNBRREC_TABLE_CONNECTION].row_ids[0] > + = {&ovnbrrec_connection_col_target, NULL, NULL}, > +}; > + > +static void > +brctl_show(struct ctl_context *ctx) > +{ > + const struct ovnbrrec_bridge *br; > + > + if (ctx->argc == 2) { > + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &br); > + if (error) { > + ctx->error = error; > + return; > + } > + > + print_br(br, &ctx->output); > + } else { > + OVNBRREC_BRIDGE_FOR_EACH (br, ctx->idl) { > + print_br(br, &ctx->output); > + } > + } > +} > + > +static void > +pre_get_info(struct ctl_context *ctx) > +{ > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_name); > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_options); > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_external_ids); > + > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_actions); > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_bridge); > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_match); > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_priority); > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_table_id); > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_external_ids); > +} > + > +static bool > +is_uuid_with_prefix(const char *uuid) > +{ > + return uuid[0] == '0' && (uuid[1] == 'x' || uuid[1] == 'X'); > +} > + > +static const char * > +strip_leading_zero(const char *s) > +{ > + return s + strspn(s, "0"); > +} > + > +static bool > +is_partial_uuid_match(const struct uuid *uuid, const char *match) > +{ > + char uuid_s[UUID_LEN + 1]; > + snprintf(uuid_s, sizeof uuid_s, UUID_FMT, UUID_ARGS(uuid)); > + > + /* We strip leading zeros because we want to accept cookie values derived > + * from UUIDs, and cookie values are printed without leading zeros because > + * they're just numbers. */ > + const char *s1 = strip_leading_zero(uuid_s); > + const char *s2 = match; > + if (is_uuid_with_prefix(s2)) { > + s2 = s2 + 2; > + } > + s2 = strip_leading_zero(s2); > + return !strncmp(s1, s2, strlen(s2)); > +} strip_leading_zero() and is_partial_uuid_match() are ripped straight from ovn-sbctl.c . These should probably be placed in ovn-util so that both utilities can reference the same functions. > + > + > +struct brctl_lflow { > + const struct ovnbrrec_logical_flow *lflow; > + const struct ovnbrrec_bridge *br; > +}; > + > +static int > +brctl_lflow_cmp(const void *a_, const void *b_) > +{ > + const struct brctl_lflow *a_ctl_lflow = a_; > + const struct brctl_lflow *b_ctl_lflow = b_; > + > + const struct ovnbrrec_logical_flow *a = a_ctl_lflow->lflow; > + const struct ovnbrrec_logical_flow *b = b_ctl_lflow->lflow; > + > + const struct ovnbrrec_bridge *abr = a_ctl_lflow->br; > + const struct ovnbrrec_bridge *bbr = b_ctl_lflow->br; > + const char *a_name = abr->name; > + const char *b_name = bbr->name; > + int cmp = strcmp(a_name, b_name); > + if (cmp) { > + return cmp; > + } > + > + cmp = uuid_compare_3way(&abr->header_.uuid, &bbr->header_.uuid); > + if (cmp) { > + return cmp; > + } > + > + cmp = (a->table_id > b->table_id ? 1 > + : a->table_id < b->table_id ? -1 > + : a->priority > b->priority ? -1 > + : a->priority < b->priority ? 1 > + : strcmp(a->match, b->match)); > + return cmp ? cmp : strcmp(a->actions, b->actions); > +} > + > +static void > +brctl_lflow_add(struct vector *lflows, > + const struct ovnbrrec_logical_flow *lflow, > + const struct ovnbrrec_bridge *br) > +{ > + struct brctl_lflow brctl_lflow = (struct brctl_lflow) { > + .lflow = lflow, > + .br = br, > + }; > + vector_push(lflows, &brctl_lflow); > +} > + > +static void > +print_uuid_part(const struct uuid *uuid, bool do_print, struct ds *s) > +{ > + if (!do_print) { > + return; > + } > + ds_put_format(s, "uuid=0x%08"PRIx32", ", uuid->parts[0]); > +} > + > +static void > +print_bridge_prompt(const struct ovnbrrec_bridge *br, > + const struct uuid *uuid, struct ds *s) { > + ds_put_format(s, "Bridge: %s", br->name); > + ds_put_format(s, " ("UUID_FMT")\n", > + UUID_ARGS(uuid)); > +} > + > +static char * OVS_WARN_UNUSED_RESULT > +parse_priority(const char *arg, int64_t *priority_p) > +{ > + /* Validate priority. */ > + int64_t priority; > + if (!ovs_scan(arg, "%"SCNd64, &priority) > + || priority < 0 || priority > 32767) { > + /* Priority_p could be uninitialized as no valid priority was > + * input, initialize it to a valid value of 0 before returning */ > + *priority_p = 0; > + return xasprintf("%s: priority must in range 0...32767", arg); > + } > + *priority_p = priority; > + return NULL; > +} > + > +static char * OVS_WARN_UNUSED_RESULT > +parse_table_id(const char *arg, int64_t *table_p) > +{ > + /* Validate table id. */ > + int64_t table; > + if (!ovs_scan(arg, "%"SCNd64, &table) > + || table < 0 || table > 55) { > + /* table_p could be uninitialized as no valid table id was > + * input, initialize it to a valid value of 0 before returning */ > + *table_p = 0; > + return xasprintf("%s: table must in range 0...55", arg); > + } > + *table_p = table; > + return NULL; > +} > + > +static char * OVS_WARN_UNUSED_RESULT > +br_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist, > + const struct ovnbrrec_bridge **br_p) > +{ > + const struct ovsdb_idl_row *row = NULL; > + > + char *error = ctl_get_row(ctx, &ovnbrrec_table_bridge, > + id, must_exist, &row); > + *br_p = (const struct ovnbrrec_bridge *) row; > + return error; > +} > + > +static void > +cmd_br_add(struct ctl_context *ctx) > +{ > + const struct ovnbrrec_bridge *bridge = NULL; > + > + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], false, &bridge); > + if (error) { > + ctl_error(ctx, "%s", error); > + free(error); > + return; > + } > + > + if (bridge) { > + ctl_error(ctx, "Bridge %s already exists", ctx->argv[1]); > + } > + > + bridge = ovnbrrec_bridge_insert(ctx->txn); > + ovnbrrec_bridge_set_name(bridge, ctx->argv[1]); > +} > + > +static void > +cmd_br_del(struct ctl_context *ctx) > +{ > + const struct ovnbrrec_bridge *bridge = NULL; > + > + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &bridge); > + if (error) { > + ctl_error(ctx, "%s", error); > + free(error); > + return; > + } > + > + ovnbrrec_bridge_delete(bridge); > +} > + > +static void > +cmd_lflow_list(struct ctl_context *ctx) > +{ > + struct vector lflows = VECTOR_EMPTY_INITIALIZER(struct brctl_lflow); > + const struct ovnbrrec_bridge *bridge = NULL; > + > + if (ctx->argc > 1) { > + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], false, &bridge); > + if (error) { > + ctl_error(ctx, "%s", error); > + free(error); > + return; > + } > + > + if (bridge) { > + ctx->argc--; > + ctx->argv++; > + } > + } > + > + const struct ovnbrrec_logical_flow *lflow; > + OVNBRREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->idl) { > + if (bridge && lflow->bridge != bridge) { > + continue; > + } > + > + brctl_lflow_add(&lflows, lflow, lflow->bridge); > + } > + > + vector_qsort(&lflows, brctl_lflow_cmp); > + > + bool print_uuid = shash_find(&ctx->options, "--uuid") != NULL; > + > + const struct brctl_lflow *curr, *prev = NULL; > + VECTOR_FOR_EACH_PTR (&lflows, curr) { > + /* Figure out whether to print this particular flow. By default, we > + * print all flows, but if any UUIDs were listed on the command line > + * then we only print the matching ones. */ > + bool include; > + if (ctx->argc > 1) { > + include = false; > + for (size_t j = 1; j < ctx->argc; j++) { > + if (is_partial_uuid_match(&curr->lflow->header_.uuid, > + ctx->argv[j])) { > + include = true; > + break; > + } > + } > + } else { > + include = true; > + } > + if (!include) { > + continue; > + } > + > + /* Print a header line for this datapath or pipeline, if we haven't > + * already done so. */ > + if (!prev || prev->br != curr->br) { > + print_bridge_prompt(curr->br, &curr->br->header_.uuid, > + &ctx->output); > + } > + > + /* Print the flow. */ > + ds_put_cstr(&ctx->output, " "); > + print_uuid_part(&curr->lflow->header_.uuid, print_uuid, &ctx->output); > + ds_put_format(&ctx->output, > + "table=%-2"PRId64", priority=%-5"PRId64 > + ", match=(%s), action=(%s)\n", > + curr->lflow->table_id, > + curr->lflow->priority, curr->lflow->match, > + curr->lflow->actions); > + prev = curr; > + } > + > + vector_destroy(&lflows); > +} > + > +static void > +cmd_lflow_add(struct ctl_context *ctx) > +{ > + const struct ovnbrrec_bridge *bridge = NULL; > + > + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &bridge); > + if (error) { > + ctl_error(ctx, "%s", error); > + free(error); > + return; > + } > + > + int64_t table_id; > + error = parse_table_id(ctx->argv[2], &table_id); > + if (error) { > + ctx->error = error; > + return; > + } > + > + int64_t priority; > + error = parse_priority(ctx->argv[3], &priority); > + if (error) { > + ctx->error = error; > + return; > + } > + > + struct ovnbrrec_logical_flow *lflow = > + ovnbrrec_logical_flow_insert(ctx->txn); > + ovnbrrec_logical_flow_set_bridge(lflow, bridge); > + ovnbrrec_logical_flow_set_table_id(lflow, table_id); > + ovnbrrec_logical_flow_set_priority(lflow, priority); > + ovnbrrec_logical_flow_set_match(lflow, ctx->argv[4]); > + ovnbrrec_logical_flow_set_actions(lflow, ctx->argv[5]); > +} > + > +static void > +cmd_lflow_del(struct ctl_context *ctx) > +{ > + const struct ovnbrrec_logical_flow *lflow; > + const struct ovsdb_idl_row *row; > + > + char *error = ctl_get_row(ctx, &ovnbrrec_table_logical_flow, > + ctx->argv[1], true, &row); > + if (error) { > + ctl_error(ctx, "%s", error); > + free(error); > + return; > + } > + > + lflow = (const struct ovnbrrec_logical_flow *) row; > + ovnbrrec_logical_flow_delete(lflow); > +} > + > +static void > +cmd_lflows_del(struct ctl_context *ctx) > +{ > + const struct ovnbrrec_bridge *bridge = NULL; > + > + if (ctx->argc > 1) { > + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &bridge); > + if (error) { > + ctl_error(ctx, "%s", error); > + free(error); > + return; > + } > + } > + > + const struct ovnbrrec_logical_flow *lflow; > + OVNBRREC_LOGICAL_FLOW_FOR_EACH_SAFE (lflow, ctx->idl) { > + if (!bridge || lflow->bridge == bridge) { > + ovnbrrec_logical_flow_delete(lflow); > + } > + } > +} > + > +static const struct ctl_command_syntax brctl_commands[] = { > + { "init", 0, 0, "", NULL, brctl_init, NULL, "", RW }, > + { "show", 0, 1, "[BRIDGE]", brctl_pre_show, brctl_show, NULL, "", RO }, > + > + /* Bridge commands. */ > + {"add-br", 1, 1, "BRIDGE", pre_get_info, cmd_br_add, NULL, > + "", RW}, > + {"del-br", 1, 1, "BRIDGE", pre_get_info, cmd_br_del, NULL, > + "", RW}, > + > + /* Logical flow commands */ > + {"lflow-list", 0, INT_MAX, "[BRIDGE] [LFLOW...]", > + pre_get_info, cmd_lflow_list, NULL, > + "--uuid,--ovs?,--stats,--vflows?", RO}, > + {"dump-flows", 0, INT_MAX, "[DATAPATH] [LFLOW...]", > + pre_get_info, cmd_lflow_list, NULL, > + "--uuid,--ovs?,--stats,--vflows?", > + RO}, /* Friendly alias for lflow-list */ > + {"add-flow", 5, 5, "BRIDGE TABLE PRIORITY MATCH ACTION", > + pre_get_info, cmd_lflow_add, NULL, > + "", RW}, > + {"del-flow", 1, 1, "UUID", pre_get_info, cmd_lflow_del, NULL, > + "", RW}, > + {"del-flows", 0, 1, "[BRIDGE]", pre_get_info, cmd_lflows_del, NULL, > + "", RW}, > + {NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, RO}, > +}; > + > +static void > +brctl_usage(void) > +{ > + printf("\ > +%s: OVN Provider DB management utility\n\ > +usage: %s [OPTIONS] COMMAND [ARG...]\n\ > +\n\ > +General commands:\n\ > + init initialize the database\n\ > + show print overview of database contents\n\ > +\n\ > +Logical flow commands:\n\ > + lflow-list [BRIDGE] [LFLOW...] list logical flows for BRIDGE\n\ > + dump-flows [BRIDGE] [LFLOW...] alias for lflow-list\n\ > +\n\ > +\n\n", program_name, program_name); > +} > + > +int > +main(int argc, char *argv[]) > +{ > + struct ovn_dbctl_options dbctl_options = { > + .db_version = ovnbrrec_get_db_version(), > + .default_db = default_br_db(), > + .allow_wait = false, > + > + .options_env_var_name = "OVN_brctl_OPTIONS", > + .daemon_env_var_name = "OVN_PR_DAEMON", > + > + .idl_class = &ovnbrrec_idl_class, > + .tables = tables, > + .cmd_show_table = NULL, > + .commands = brctl_commands, > + > + .usage = brctl_usage, > + .add_base_prerequisites = brctl_add_base_prerequisites, > + .pre_execute = brctl_pre_execute, > + .post_execute = NULL, > + .get_inactivity_probe = get_inactivity_probe, > + > + .ctx_create = brctl_ctx_create, > + .ctx_destroy = brctl_ctx_destroy, > + }; > + > + return ovn_dbctl_main(argc, argv, &dbctl_options); > +}
On Thu, Aug 21, 2025 at 5:12 PM Mark Michelson <mmichels@redhat.com> wrote: > > Hi Numan, > Hi Mark, First of all, I'm really sorry for the late replies. Thanks for providing your comments for this patch series. Please see below for my resplies. > The first 11 patches in this series fail CI because of failures to `make > rpm-fedora`. I think some of the specfile changes from patch 12 should > be added to earlier patches in order to allow CI to succeed. Ack. I'll move patch 12 to patch 1 so that CI passes from patch 1 itself. > > I have more below. > > On 8/11/25 6:09 AM, numans@ovn.org wrote: > > From: Numan Siddique <numans@ovn.org> > > > > This commit is the first of the series to add a new service > > to OVN called ovn-br-controller (OVN bridge controller). > > This commit adds > > - a schema file > > - a basic skeleton of ovn-br-controller service (which does nothing) > > - ovn-brctl utility similar to ovn-nbctl/ovn-sbctl. > > > > Rational for adding this service: > > There's a need to configure the provider bridges with specific OpenFlow > > rules after packets leave the OVN pipeline and enters these provider > > bridges via the patch ports. Since OVN has a very good abstraction > > of the OpenFlow rules using OVN logical flows, we can leverage the same > > here so that CMS doesn't have to deal with the specifics of OpenFlow > > protocol. Also if the CMS is written in golang or other languages, > > CMS has to mostly rely on ovs-vsctl/ovs-ofctl to program the flows. > > Adding this support in OVN avoids the CMS to exec these utils to > > add/delete and dump the existing OpenFlow rules. > > > > Any user can also use this new service to program and manage any OVS > > bridge without using OVN and hence this service is named as > > "ovn-br-controller." > > > > Signed-off-by: Numan Siddique <numans@ovn.org> > > --- > > Makefile.am | 5 +- > > automake.mk | 36 ++ > > br-controller/automake.mk | 8 + > > br-controller/ovn-br-controller.8.xml | 5 + > > br-controller/ovn-br-controller.c | 175 ++++++++ > > lib/automake.mk | 17 +- > > lib/ovn-br-idl.ann | 9 + > > lib/ovn-util.c | 13 + > > lib/ovn-util.h | 1 + > > ovn-br.ovsschema | 94 +++++ > > ovn-br.xml | 452 ++++++++++++++++++++ > > tutorial/ovn-sandbox | 24 +- > > utilities/automake.mk | 8 + > > utilities/ovn-brctl.c | 585 ++++++++++++++++++++++++++ > > 14 files changed, 1429 insertions(+), 3 deletions(-) > > create mode 100644 br-controller/automake.mk > > create mode 100644 br-controller/ovn-br-controller.8.xml > > create mode 100644 br-controller/ovn-br-controller.c > > create mode 100644 lib/ovn-br-idl.ann > > create mode 100644 ovn-br.ovsschema > > create mode 100644 ovn-br.xml > > create mode 100644 utilities/ovn-brctl.c > > > > diff --git a/Makefile.am b/Makefile.am > > index ea98fb5fb5..1936e9697a 100644 > > --- a/Makefile.am > > +++ b/Makefile.am > > @@ -115,7 +115,9 @@ EXTRA_DIST = \ > > ovn-ic-nb.ovsschema \ > > ovn-ic-nb.xml \ > > ovn-ic-sb.ovsschema \ > > - ovn-ic-sb.xml > > + ovn-ic-sb.xml \ > > + ovn-br.ovsschema \ > > + ovn-br.xml > > bin_PROGRAMS = > > sbin_PROGRAMS = > > bin_SCRIPTS = > > @@ -507,4 +509,5 @@ include controller/automake.mk > > include controller-vtep/automake.mk > > include northd/automake.mk > > include ic/automake.mk > > +include br-controller/automake.mk > > include build-aux/automake.mk > > diff --git a/automake.mk b/automake.mk > > index a7947a3f54..53860ad89b 100644 > > --- a/automake.mk > > +++ b/automake.mk > > @@ -120,6 +120,35 @@ ovn-ic-sb.5: \ > > $(srcdir)/ovn-ic-sb.xml > $@.tmp && \ > > mv $@.tmp $@ > > > > +# OVN bridge controller E-R diagram > > +# > > +# If "python" or "dot" is not available, then we do not add graphical diagram > > +# to the documentation. > > +if HAVE_DOT > > +ovn-br.gv: ${OVSDIR}/ovsdb/ovsdb-dot.in $(srcdir)/ovn-br.ovsschema > > + $(AM_V_GEN)$(OVSDB_DOT) --no-arrows $(srcdir)/ovn-br.ovsschema > $@ > > +ovn-br.pic: ovn-br.gv ${OVSDIR}/ovsdb/dot2pic > > + $(AM_V_GEN)(dot -T plain < ovn-br.gv | $(PYTHON3) ${OVSDIR}/ovsdb/dot2pic -f 3) > $@.tmp && \ > > + mv $@.tmp $@ > > +OVN_BR_PIC = ovn-br.pic > > +OVN_BR_DOT_DIAGRAM_ARG = --er-diagram=$(OVN_BR_PIC) > > +CLEANFILES += ovn-br.gv ovn-br.pic > > +endif > > + > > +# OVN bridge controller schema documentation > > +EXTRA_DIST += ovn-br.xml > > +CLEANFILES += ovn-br.5 > > +man_MANS += ovn-br.5 > > + > > +ovn-br.5: \ > > + ${OVSDIR}/ovsdb/ovsdb-doc $(srcdir)/ovn-br.xml $(srcdir)/ovn-br.ovsschema $(OVN_BR_PIC) > > + $(AM_V_GEN)$(OVSDB_DOC) \ > > + $(OVN_BR_DOT_DIAGRAM_ARG) \ > > + --version=$(VERSION) \ > > + $(srcdir)/ovn-br.ovsschema \ > > + $(srcdir)/ovn-br.xml > $@.tmp && \ > > + mv $@.tmp $@ > > + > > # Version checking for ovn-nb.ovsschema. > > ALL_LOCAL += ovn-nb.ovsschema.stamp > > ovn-nb.ovsschema.stamp: ovn-nb.ovsschema > > @@ -143,9 +172,16 @@ ovn-ic-sb.ovsschema.stamp: ovn-ic-sb.ovsschema > > $(srcdir)/build-aux/cksum-schema-check $? $@ > > CLEANFILES += ovn-ic-sb.ovsschema.stamp > > > > +# Version checking for ovn-br.ovsschema. > > +ALL_LOCAL += ovn-br.ovsschema.stamp > > +ovn-br.ovsschema.stamp: ovn-br.ovsschema > > + $(srcdir)/build-aux/cksum-schema-check $? $@ > > +CLEANFILES += ovn-br.ovsschema.stamp > > + > > pkgdata_DATA += ovn-nb.ovsschema > > pkgdata_DATA += ovn-sb.ovsschema > > pkgdata_DATA += ovn-ic-nb.ovsschema > > pkgdata_DATA += ovn-ic-sb.ovsschema > > +pkgdata_DATA += ovn-br.ovsschema > > > > CLEANFILES += ovn-sb.ovsschema.stamp > > diff --git a/br-controller/automake.mk b/br-controller/automake.mk > > new file mode 100644 > > index 0000000000..012e9a5ed8 > > --- /dev/null > > +++ b/br-controller/automake.mk > > @@ -0,0 +1,8 @@ > > +bin_PROGRAMS += br-controller/ovn-br-controller > > +br_controller_ovn_br_controller_SOURCES = \ > > + br-controller/ovn-br-controller.c > > + > > +br_controller_ovn_br_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la > > +man_MANS += br-controller/ovn-br-controller.8 > > +EXTRA_DIST += br-controller/ovn-br-controller.8.xml > > +CLEANFILES += br-controller/ovn-br-controller.8 > > diff --git a/br-controller/ovn-br-controller.8.xml b/br-controller/ovn-br-controller.8.xml > > new file mode 100644 > > index 0000000000..fa6eb2e2e2 > > --- /dev/null > > +++ b/br-controller/ovn-br-controller.8.xml > > @@ -0,0 +1,5 @@ > > +<?xml version="1.0" encoding="utf-8"?> > > +<manpage program="ovn-br-controller" section="8" title="ovn-br-controller"> > > + <h1>Name</h1> > > + <p>ovn-br-controller -- Open Virtual Network local OVS bridge controller</p> > > +</manpage> > > diff --git a/br-controller/ovn-br-controller.c b/br-controller/ovn-br-controller.c > > new file mode 100644 > > index 0000000000..0fef0e5fee > > --- /dev/null > > +++ b/br-controller/ovn-br-controller.c > > @@ -0,0 +1,175 @@ > > +/* Copyright (c) 2025 Crusoe Energy Systems LLC > > + * > > + * Licensed under the Apache License, Version 2.0 (the "License"); > > + * you may not use this file except in compliance with the License. > > + * You may obtain a copy of the License at: > > + * > > + * http://www.apache.org/licenses/LICENSE-2.0 > > + * > > + * Unless required by applicable law or agreed to in writing, software > > + * distributed under the License is distributed on an "AS IS" BASIS, > > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. > > + * See the License for the specific language governing permissions and > > + * limitations under the License. > > + */ > > + > > +#include <config.h> > > + > > +#include <errno.h> > > +#include <getopt.h> > > +#include <signal.h> > > +#include <stdlib.h> > > +#include <string.h> > > + > > +/* OVS includes. */ > > +#include "openvswitch/vlog.h" > > +#include "lib/command-line.h" > > +#include "lib/daemon.h" > > +#include "lib/dirs.h" > > +#include "lib/fatal-signal.h" > > +#include "lib/stream.h" > > +#include "lib/stream-ssl.h" > > +#include "lib/unixctl.h" > > + > > +/* OVN includes. */ > > +#include "lib/ovn-br-idl.h" > > +#include "lib/ovn-util.h" > > + > > +VLOG_DEFINE_THIS_MODULE(main); > > + > > +static void parse_options(int argc, char *argv[]); > > +OVS_NO_RETURN static void usage(void); > > + > > + > > +/* SSL/TLS options. */ > > +static const char *ssl_private_key_file; > > +static const char *ssl_certificate_file; > > +static const char *ssl_ca_cert_file; > > + > > +/* --unixctl-path: Path to use for unixctl server socket. */ > > +static char *unixctl_path; > > + > > +int > > +main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) > > +{ > > + ovs_cmdl_proctitle_init(argc, argv); > > + ovn_set_program_name(argv[0]); > > + service_start(&argc, &argv); > > + parse_options(argc, argv); > > + fatal_ignore_sigpipe(); > > + > > + return 0; > > +} > > + > > +/* static functions. */ > > +static void > > +parse_options(int argc, char *argv[]) > > +{ > > + enum { > > + OPT_PEER_CA_CERT = UCHAR_MAX + 1, > > + OPT_BOOTSTRAP_CA_CERT, > > + VLOG_OPTION_ENUMS, > > + OVN_DAEMON_OPTION_ENUMS, > > + SSL_OPTION_ENUMS, > > + }; > > + > > + static struct option long_options[] = { > > + {"help", no_argument, NULL, 'h'}, > > + {"version", no_argument, NULL, 'V'}, > > + {"unixctl", required_argument, NULL, 'u'}, > > + VLOG_LONG_OPTIONS, > > + OVN_DAEMON_LONG_OPTIONS, > > + STREAM_SSL_LONG_OPTIONS, > > + {"peer-ca-cert", required_argument, NULL, OPT_PEER_CA_CERT}, > > + {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT}, > > + {NULL, 0, NULL, 0} > > + }; > > + char *short_options = ovs_cmdl_long_options_to_short_options(long_options); > > + > > + for (;;) { > > + int c; > > + > > + c = getopt_long(argc, argv, short_options, long_options, NULL); > > + if (c == -1) { > > + break; > > + } > > + > > + switch (c) { > > + case 'h': > > + usage(); > > + > > + case 'V': > > + ovs_print_version(OFP15_VERSION, OFP15_VERSION); > > + printf("OVN BR DB Schema %s\n", ovnbrrec_get_db_version()); > > + exit(EXIT_SUCCESS); > > + > > + case 'u': > > + unixctl_path = optarg; > > + break; > > + > > + VLOG_OPTION_HANDLERS > > + OVN_DAEMON_OPTION_HANDLERS > > + > > + case 'p': > > + ssl_private_key_file = optarg; > > + break; > > + > > + case 'c': > > + ssl_certificate_file = optarg; > > + break; > > + > > + case 'C': > > + ssl_ca_cert_file = optarg; > > + break; > > + > > + case OPT_SSL_PROTOCOLS: > > + stream_ssl_set_protocols(optarg); > > + break; > > + > > + case OPT_SSL_CIPHERS: > > + stream_ssl_set_ciphers(optarg); > > + break; > > + > > + case OPT_SSL_CIPHERSUITES: > > + stream_ssl_set_ciphersuites(optarg); > > + break; > > + > > + case OPT_PEER_CA_CERT: > > + stream_ssl_set_peer_ca_cert_file(optarg); > > + break; > > + > > + case OPT_BOOTSTRAP_CA_CERT: > > + stream_ssl_set_ca_cert_file(optarg, true); > > + break; > > + > > + case '?': > > + exit(EXIT_FAILURE); > > + > > + default: > > + ovs_abort(0, "Invalid option."); > > + } > > + } > > + free(short_options); > > + > > + argc -= optind; > > + argv += optind; > > +} > > + > > +static void > > +usage(void) > > +{ > > + printf("%s: OVN bridge controller\n" > > + "usage %s [OPTIONS] [OVS-DATABASE]\n" > > + "where OVS-DATABASE is a socket on which the OVS OVSDB server " > > + "is listening.\n", > > + program_name, program_name); > > + stream_usage("OVS-DATABASE", true, false, true); > > + daemon_usage(); > > + vlog_usage(); > > + printf("\nOther options:\n" > > + " -u, --unixctl=SOCKET set control socket name\n" > > + " -n custom chassis name\n" > > + " -h, --help display this help message\n" > > + " -V, --version display version information\n"); > > + exit(EXIT_SUCCESS); > > +} > > diff --git a/lib/automake.mk b/lib/automake.mk > > index a59c722d62..46c24e99d1 100644 > > --- a/lib/automake.mk > > +++ b/lib/automake.mk > > @@ -59,7 +59,9 @@ nodist_lib_libovn_la_SOURCES = \ > > lib/ovn-ic-nb-idl.c \ > > lib/ovn-ic-nb-idl.h \ > > lib/ovn-ic-sb-idl.c \ > > - lib/ovn-ic-sb-idl.h > > + lib/ovn-ic-sb-idl.h \ > > + lib/ovn-br-idl.c \ > > + lib/ovn-br-idl.h > > > > CLEANFILES += $(nodist_lib_libovn_la_SOURCES) > > > > @@ -129,3 +131,16 @@ OVN_IC_SB_IDL_FILES = \ > > lib/ovn-ic-sb-idl.ovsidl: $(OVN_IC_SB_IDL_FILES) > > $(AM_V_GEN)$(OVSDB_IDLC) annotate $(OVN_IC_SB_IDL_FILES) > $@.tmp && \ > > mv $@.tmp $@ > > + > > +# ovn-br IDL > > +OVSIDL_BUILT += \ > > + lib/ovn-br-idl.c \ > > + lib/ovn-br-idl.h \ > > + lib/ovn-br-idl.ovsidl > > +EXTRA_DIST += lib/ovn-br-idl.ann > > +OVN_PR_IDL_FILES = \ > > + $(srcdir)/ovn-br.ovsschema \ > > + $(srcdir)/lib/ovn-br-idl.ann > > +lib/ovn-br-idl.ovsidl: $(OVN_PR_IDL_FILES) > > + $(AM_V_GEN)$(OVSDB_IDLC) annotate $(OVN_PR_IDL_FILES) > $@.tmp && \ > > + mv $@.tmp $@ > > diff --git a/lib/ovn-br-idl.ann b/lib/ovn-br-idl.ann > > new file mode 100644 > > index 0000000000..80993338a6 > > --- /dev/null > > +++ b/lib/ovn-br-idl.ann > > @@ -0,0 +1,9 @@ > > +# -*- python -*- > > + > > +# This code, when invoked by "ovsdb-idlc annotate" (by the build > > +# process), annotates ovn-br.ovsschema with additional data that give > > +# the ovsdb-idl engine information about the types involved, so that > > +# it can generate more programmer-friendly data structures. > > + > > +s["idlPrefix"] = "ovnbrrec_" > > +s["idlHeader"] = "\"lib/ovn-br-idl.h\"" > > diff --git a/lib/ovn-util.c b/lib/ovn-util.c > > index ab5c4fc477..1cfeed6044 100644 > > --- a/lib/ovn-util.c > > +++ b/lib/ovn-util.c > > @@ -560,6 +560,19 @@ default_ic_sb_db(void) > > return def; > > } > > > > +const char * > > +default_br_db(void) > > +{ > > + static char *def; > > + if (!def) { > > + def = getenv("OVN_BR_DB"); > > + if (!def) { > > + def = xasprintf("unix:%s/ovnbr_db.sock", ovn_rundir()); > > + } > > + } > > + return def; > > +} > > + > > char * > > get_abs_unix_ctl_path(const char *path) > > { > > diff --git a/lib/ovn-util.h b/lib/ovn-util.h > > index bde40666a1..c66d7c31ff 100644 > > --- a/lib/ovn-util.h > > +++ b/lib/ovn-util.h > > @@ -131,6 +131,7 @@ const char *default_nb_db(void); > > const char *default_sb_db(void); > > const char *default_ic_nb_db(void); > > const char *default_ic_sb_db(void); > > +const char *default_br_db(void); > > char *get_abs_unix_ctl_path(const char *path); > > > > struct ovsdb_idl_table_class; > > diff --git a/ovn-br.ovsschema b/ovn-br.ovsschema > > new file mode 100644 > > index 0000000000..5635ee5bbd > > --- /dev/null > > +++ b/ovn-br.ovsschema > > @@ -0,0 +1,94 @@ > > +{ > > + "name": "OVN_Bridge_Controller", > > + "version": "0.0.1", > > + "cksum": "124113656 4389", > > + "tables": { > > + "BR_Global": { > > + "columns": { > > + "br_cfg": {"type": {"key": "integer"}}, > > + "external_ids": { > > + "type": {"key": "string", "value": "string", > > + "min": 0, "max": "unlimited"}}, > > + "connections": { > > + "type": {"key": {"type": "uuid", > > + "refTable": "Connection"}, > > + "min": 0, > > + "max": "unlimited"}}, > > + "ssl": { > > + "type": {"key": {"type": "uuid", > > + "refTable": "SSL"}, > > + "min": 0, "max": 1}}, > > + "options": { > > + "type": {"key": "string", "value": "string", > > + "min": 0, "max": "unlimited"}}}, > > + "maxRows": 1, > > + "isRoot": true}, > > + "Connection": { > > + "columns": { > > + "target": {"type": "string"}, > > + "max_backoff": {"type": {"key": {"type": "integer", > > + "minInteger": 1000}, > > + "min": 0, > > + "max": 1}}, > > + "inactivity_probe": {"type": {"key": "integer", > > + "min": 0, > > + "max": 1}}, > > + "other_config": {"type": {"key": "string", > > + "value": "string", > > + "min": 0, > > + "max": "unlimited"}}, > > + "external_ids": {"type": {"key": "string", > > + "value": "string", > > + "min": 0, > > + "max": "unlimited"}}, > > + "is_connected": {"type": "boolean", "ephemeral": true}, > > + "status": {"type": {"key": "string", > > + "value": "string", > > + "min": 0, > > + "max": "unlimited"}, > > + "ephemeral": true}}, > > + "indexes": [["target"]]}, > > + "SSL": { > > + "columns": { > > + "private_key": {"type": "string"}, > > + "certificate": {"type": "string"}, > > + "ca_cert": {"type": "string"}, > > + "bootstrap_ca_cert": {"type": "boolean"}, > > + "ssl_protocols": {"type": "string"}, > > + "ssl_ciphers": {"type": "string"}, > > + "ssl_ciphersuites": {"type": "string"}, > > + "external_ids": {"type": {"key": "string", > > + "value": "string", > > + "min": 0, > > + "max": "unlimited"}}}, > > + "maxRows": 1}, > > + "Bridge": { > > + "columns": { > > + "name": {"type": "string"}, > > + "options": { > > + "type": {"key": "string", "value": "string", > > + "min": 0, "max": "unlimited"}}, > > + "external_ids": { > > + "type": {"key": "string", "value": "string", > > + "min": 0, "max": "unlimited"}}}, > > + "indexes": [["name"]], "> > + "isRoot": true}, > > + "Logical_Flow": { > > + "columns": { > > + "bridge": {"type": {"key": {"type": "uuid", > > + "refTable": "Bridge"}, > > + "min": 0, "max": 1}}, > > Out of curiosity, what happens if you run ovn-brcontroller on the same > hypervisor where ovn-controller is running, then you create a Bridge in > the BR DB for "br-int" and you add logical flows to the BR DB for "br-int"? > Should there be safeguards in the code to prevent this? I agree. In v2 of this series, I'll handle it. We can't stop the user from adding "br-int" (or integration bridge managed by ovn-controller) to the Bridge table. ovn-bridge-controller should read the "ovn-bridge" option from the OVS config and put a warning that this bridge cannot be managed as ovn-controller manages it. I'll handle that in patch 4 of v2. > > > + "table_id": {"type": {"key": {"type": "integer", > > + "minInteger": 0, > > + "maxInteger": 32}}}, > > Why is the table_id limited to 32? We don't have ingress and egress > pipelines, so the table_id should have a larger range for these logical > flows. Agree. I'll set it to 100 for now. Let me know we should increase it further. > > > + "priority": {"type": {"key": {"type": "integer", > > + "minInteger": 0, > > + "maxInteger": 65535}}}, > > + "match": {"type": "string"}, > > + "actions": {"type": "string"}, > > + "external_ids": { > > + "type": {"key": "string", "value": "string", > > + "min": 0, "max": "unlimited"}}}, > > + "isRoot": true} > > + } > > +} > > diff --git a/ovn-br.xml b/ovn-br.xml > > new file mode 100644 > > index 0000000000..be6232094b > > --- /dev/null > > +++ b/ovn-br.xml > > @@ -0,0 +1,452 @@ > > +<?xml version="1.0" encoding="utf-8"?> > > +<database name="ovn-bridge-controller" title="OVN Bridge Controller Database"> > > + <p> > > + This database is the interface between OVN Bridge Controller and the > > + cloud management system (CMS) to program and control the OVS bridges > > + using OVN logical flows. The CMS produces almost all of > > + the contents of the database. The <code>ovn-bridge-controller</code> program > > + monitors the database contents and programs the OVS bridges with the > > + OpenFlow rules. > > + </p> > > + > > + <h2>External IDs</h2> > > + > > + <p> > > + Each of the tables in this database contains a special column, named > > + <code>external_ids</code>. This column has the same form and purpose each > > + place it appears. > > + </p> > > + > > + <dl> > > + <dt><code>external_ids</code>: map of string-string pairs</dt> > > + <dd> > > + Key-value pairs for use by the CMS. The CMS might use certain pairs, for > > + example, to identify entities in its own configuration that correspond to > > + those in this database. > > + </dd> > > + </dl> > > + > > + <table name="BR_Global" title="Bridge Controller configuration"> > > + <column name="br_cfg"> > > + Sequence number for client to increment. When a client modifies any > > + part of the bridge Controller database configuration and wishes to wait > > + for <code>ovn-br-controller</code> to finish applying the changes, it may > > + increment this sequence number. > > + </column> > > + > > + <group title="Common Columns"> > > + <column name="external_ids"> > > + See <em>External IDs</em> at the beginning of this document. > > + </column> > > + </group> > > + > > + <group title="Common options"> > > + <column name="options"> > > + This column provides general key/value settings. The supported > > + options are described individually below. > > + </column> > > + </group> > > + > > + <group title="Connection Options"> > > + <column name="connections"> > > + Database clients to which the Open vSwitch database server should > > + connect or on which it should listen, along with options for how these > > + connections should be configured. See the <ref table="Connection"/> > > + table for more information. > > + </column> > > + <column name="ssl"> > > + Global SSL/TLS configuration. > > + </column> > > + </group> > > + </table> > > + > > + <table name="Connection" title="OVSDB client connections."> > > + <p> > > + Configuration for a database connection to an Open vSwitch database > > + (OVSDB) client. > > + </p> > > + > > + <p> > > + This table primarily configures the Open vSwitch database server > > + (<code>ovsdb-server</code>). > > + </p> > > + > > + <p> > > + The Open vSwitch database server can initiate and maintain active > > + connections to remote clients. It can also listen for database > > + connections. > > + </p> > > + > > + <group title="Core Features"> > > + <column name="target"> > > + <p>Connection methods for clients.</p> > > + <p> > > + The following connection methods are currently supported: > > + </p> > > + <dl> > > + <dt><code>ssl:<var>host</var></code>[<code>:<var>port</var></code>]</dt> > > + <dd> > > + <p> > > + The specified SSL/TLS <var>port</var> on the host at the given > > + <var>host</var>, which can either be a DNS name (if built with > > + unbound library) or an IP address. A valid SSL/TLS configuration > > + must be provided when this form is used, this configuration can > > + be specified via command-line options or the <ref table="SSL"/> > > + table. > > + </p> > > + <p> > > + If <var>port</var> is not specified, it defaults to 6640. > > + </p> > > + <p> > > + SSL/TLS support is an optional feature that is not always > > + built as part of OVN or Open vSwitch. > > + </p> > > + </dd> > > + > > + <dt><code>tcp:<var>host</var></code>[<code>:<var>port</var></code>]</dt> > > + <dd> > > + <p> > > + The specified TCP <var>port</var> on the host at the given > > + <var>host</var>, which can either be a DNS name (if built with > > + unbound library) or an IP address. If <var>host</var> is an IPv6 > > + address, wrap it in square brackets, e.g. <code>tcp:[::1]:6640</code>. > > + </p> > > + <p> > > + If <var>port</var> is not specified, it defaults to 6640. > > + </p> > > + </dd> > > + <dt><code>pssl:</code>[<var>port</var>][<code>:<var>host</var></code>]</dt> > > + <dd> > > + <p> > > + Listens for SSL/TLS connections on the specified TCP > > + <var>port</var>. > > + Specify 0 for <var>port</var> to have the kernel automatically > > + choose an available port. If <var>host</var>, which can either > > + be a DNS name (if built with unbound library) or an IP address, > > + is specified, then connections are restricted to the resolved or > > + specified local IPaddress (either IPv4 or IPv6 address). If > > + <var>host</var> is an IPv6 address, wrap in square brackets, > > + e.g. <code>pssl:6640:[::1]</code>. If <var>host</var> is not > > + specified then it listens only on IPv4 (but not IPv6) addresses. > > + A valid SSL/TLS configuration must be provided when this form is > > + used, this can be specified either via command-line options or > > + the <ref table="SSL"/> table. > > + </p> > > + <p> > > + If <var>port</var> is not specified, it defaults to 6640. > > + </p> > > + <p> > > + SSL/TLS support is an optional feature that is not always built > > + as part of OVN or Open vSwitch. > > + </p> > > + </dd> > > + <dt><code>ptcp:</code>[<var>port</var>][<code>:<var>host</var></code>]</dt> > > + <dd> > > + <p> > > + Listens for connections on the specified TCP <var>port</var>. > > + Specify 0 for <var>port</var> to have the kernel automatically > > + choose an available port. If <var>host</var>, which can either > > + be a DNS name (if built with unbound library) or an IP address, > > + is specified, then connections are restricted to the resolved or > > + specified local IP address (either IPv4 or IPv6 address). If > > + <var>host</var> is an IPv6 address, wrap it in square brackets, > > + e.g. <code>ptcp:6640:[::1]</code>. If <var>host</var> is not > > + specified then it listens only on IPv4 addresses. > > + </p> > > + <p> > > + If <var>port</var> is not specified, it defaults to 6640. > > + </p> > > + </dd> > > + </dl> > > + <p>When multiple clients are configured, the <ref column="target"/> > > + values must be unique. Duplicate <ref column="target"/> values yield > > + unspecified results.</p> > > + </column> > > + </group> > > + > > + <group title="Client Failure Detection and Handling"> > > + <column name="max_backoff"> > > + Maximum number of milliseconds to wait between connection attempts. > > + Default is implementation-specific. > > + </column> > > + > > + <column name="inactivity_probe"> > > + Maximum number of milliseconds of idle time on connection to the client > > + before sending an inactivity probe message. If Open vSwitch does not > > + communicate with the client for the specified number of seconds, it > > + will send a probe. If a response is not received for the same > > + additional amount of time, Open vSwitch assumes the connection has been > > + broken and attempts to reconnect. Default is implementation-specific. > > + A value of 0 disables inactivity probes. > > + </column> > > + </group> > > + > > + <group title="Status"> > > + <p> > > + Key-value pair of <ref column="is_connected"/> is always updated. > > + Other key-value pairs in the status columns may be updated depends > > + on the <ref column="target"/> type. > > + </p> > > + > > + <p> > > + When <ref column="target"/> specifies a connection method that > > + listens for inbound connections (e.g. <code>ptcp:</code> or > > + <code>punix:</code>), both <ref column="n_connections"/> and > > + <ref column="is_connected"/> may also be updated while the > > + remaining key-value pairs are omitted. > > + </p> > > + > > + <p> > > + On the other hand, when <ref column="target"/> specifies an > > + outbound connection, all key-value pairs may be updated, except > > + the above-mentioned two key-value pairs associated with inbound > > + connection targets. They are omitted. > > + </p> > > + > > + <column name="is_connected"> > > + <code>true</code> if currently connected to this client, > > + <code>false</code> otherwise. > > + </column> > > + > > + <column name="status" key="last_error"> > > + A human-readable description of the last error on the connection > > + to the manager; i.e. <code>strerror(errno)</code>. This key > > + will exist only if an error has occurred. > > + </column> > > + > > + <column name="status" key="state" > > + type='{"type": "string", "enum": ["set", ["VOID", "BACKOFF", "CONNECTING", "ACTIVE", "IDLE"]]}'> > > + <p> > > + The state of the connection to the manager: > > + </p> > > + <dl> > > + <dt><code>VOID</code></dt> > > + <dd>Connection is disabled.</dd> > > + > > + <dt><code>BACKOFF</code></dt> > > + <dd>Attempting to reconnect at an increasing period.</dd> > > + > > + <dt><code>CONNECTING</code></dt> > > + <dd>Attempting to connect.</dd> > > + > > + <dt><code>ACTIVE</code></dt> > > + <dd>Connected, remote host responsive.</dd> > > + > > + <dt><code>IDLE</code></dt> > > + <dd>Connection is idle. Waiting for response to keep-alive.</dd> > > + </dl> > > + <p> > > + These values may change in the future. They are provided only for > > + human consumption. > > + </p> > > + </column> > > + > > + <column name="status" key="sec_since_connect" > > + type='{"type": "integer", "minInteger": 0}'> > > + The amount of time since this client last successfully connected > > + to the database (in seconds). Value is empty if client has never > > + successfully been connected. > > + </column> > > + > > + <column name="status" key="sec_since_disconnect" > > + type='{"type": "integer", "minInteger": 0}'> > > + The amount of time since this client last disconnected from the > > + database (in seconds). Value is empty if client has never > > + disconnected. > > + </column> > > + > > + <column name="status" key="locks_held"> > > + Space-separated list of the names of OVSDB locks that the connection > > + holds. Omitted if the connection does not hold any locks. > > + </column> > > + > > + <column name="status" key="locks_waiting"> > > + Space-separated list of the names of OVSDB locks that the connection is > > + currently waiting to acquire. Omitted if the connection is not waiting > > + for any locks. > > + </column> > > + > > + <column name="status" key="locks_lost"> > > + Space-separated list of the names of OVSDB locks that the connection > > + has had stolen by another OVSDB client. Omitted if no locks have been > > + stolen from this connection. > > + </column> > > + > > + <column name="status" key="n_connections" > > + type='{"type": "integer", "minInteger": 2}'> > > + When <ref column="target"/> specifies a connection method that > > + listens for inbound connections (e.g. <code>ptcp:</code> or > > + <code>pssl:</code>) and more than one connection is actually active, > > + the value is the number of active connections. Otherwise, this > > + key-value pair is omitted. > > + </column> > > + > > + <column name="status" key="bound_port" type='{"type": "integer"}'> > > + When <ref column="target"/> is <code>ptcp:</code> or > > + <code>pssl:</code>, this is the TCP port on which the OVSDB server is > > + listening. (This is particularly useful when <ref > > + column="target"/> specifies a port of 0, allowing the kernel to > > + choose any available port.) > > + </column> > > + </group> > > + > > + <group title="Common Columns"> > > + The overall purpose of these columns is described under <code>Common > > + Columns</code> at the beginning of this document. > > + > > + <column name="external_ids"/> > > + <column name="other_config"/> > > + </group> > > + </table> > > + > > + <table name="SSL"> > > + SSL/TLS configuration for ovn-nb database access. > > + > > + <column name="private_key"> > > + Name of a PEM file containing the private key used as the switch's > > + identity for SSL/TLS connections to the controller. > > + </column> > > + > > + <column name="certificate"> > > + Name of a PEM file containing a certificate, signed by the > > + certificate authority (CA) used by the controller and manager, > > + that certifies the switch's private key, identifying a trustworthy > > + switch. > > + </column> > > + > > + <column name="ca_cert"> > > + Name of a PEM file containing the CA certificate used to verify > > + that the switch is connected to a trustworthy controller. > > + </column> > > + > > + <column name="bootstrap_ca_cert"> > > + If set to <code>true</code>, then Open vSwitch will attempt to > > + obtain the CA certificate from the controller on its first SSL/TLS > > + 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/TLS connections must be authenticated by a certificate signed > > + by the CA certificate thus obtained. <em>This option exposes the > > + SSL/TLS connection to a man-in-the-middle attack obtaining the initial > > + CA certificate.</em> It may still be useful for bootstrapping. > > + </column> > > + > > + <column name="ssl_protocols"> > > + <p> > > + Range or a comma- or space-delimited list of the SSL/TLS protocols to > > + enable for SSL/TLS connections. > > + </p> > > + <p> > > + Supported protocols include <code>TLSv1.2</code> and > > + <code>TLSv1.3</code>. Ranges can be provided in a form of two protocol > > + names separated with a dash (<code>TLSv1.2-TLSv1.3</code>), or as a > > + single protocol name with a plus sign (<code>TLSv1.2+</code>). The > > + value can be a list of protocols or exactly one range. The range is a > > + preferred way of specifying protocols and the configuration always > > + behaves as if the range between the minimum and the maximum specified > > + version is provided, i.e., if the value is set to > > + <code>TLSv1.X,TLSv1.(X+2)</code>, the <code>TLSv1.(X+1)</code> will > > + also be enabled as if it was a range. > > + Regardless of order, the highest protocol supported by both sides will > > + be chosen when making the connection. > > + </p> > > + <p> > > + The default when this option is omitted is <code>TLSv1.2+</code>. > > + </p> > > + </column> > > + > > + <column name="ssl_ciphers"> > > + List of ciphers (in OpenSSL cipher string format) to be supported > > + for SSL/TLS connections with TLSv1.2. The default when this option > > + is omitted is <code>DEFAULT:@SECLEVEL=2</code>. > > + </column> > > + > > + <column name="ssl_ciphersuites"> > > + List of ciphersuites (in OpenSSL ciphersuites string format) to be > > + supported for SSL/TLS connections with TLSv1.3 and later. Default value > > + from OpenSSL will be used when this option is omitted. > > + </column> > > + > > + <group title="Common Columns"> > > + The overall purpose of these columns is described under <code>Common > > + Columns</code> at the beginning of this document. > > + > > + <column name="external_ids"/> > > + </group> > > + </table> > > + > > + <table name="Bridge" title="OVS Bridge to control"> > > + <column name="name"> > > + Name of the OVS bridge. This bridge should exist in the local OVS > > + database. > > + </column> > > + > > + <column name="options"> > > + Reserved for future use. > > + </column> > > + > > + <column name="external_ids"> > > + See <em>External IDs</em> at the beginning of this document. > > + </column> > > + </table> > > + > > + <table name="Logical_Flow" title="Logical flow"> > > + <column name="bridge"> > > + The bridge to which the logical flow belongs to. > > + </column> > > + > > + <column name="table_id"> > > + The stage in the logical pipeline, analogous to an OpenFlow table number. > > + </column> > > + > > + <column name="priority"> > > + The flow's priority. Flows with numerically higher priority take > > + precedence over those with lower. If two logical flows with the > > + same priority both match, then the one actually applied to the packet is > > + undefined. > > + </column> > > + > > + <column name="match"> > > + <p> > > + A matching expression. OVN provides a superset of OpenFlow matching > > + capabilities, using a syntax similar to Boolean expressions in a > > + programming language. > > + </p> > > + > > + <p> > > + Please see the documentation of the > > + <ref > > + column="match" table="Logical_Flow" db="OVN_Southbound"/> > > + column of the <ref table="Logical_Flow" > > + db="OVN_Southbound"/> table in the <ref db="OVN_Southbound"/> database. > > + </p> > > + </column> > > + > > + <column name="actions"> > > + <p> > > + Logical datapath actions, to be executed when the logical flow > > + represented by this row is the highest-priority match. > > + </p> > > + > > + <p> > > + Actions share lexical syntax with the <ref column="match"/> column. An > > + empty set of actions (or one that contains just white space or > > + comments), or a set of actions that consists of just > > + <code>drop;</code>, causes the matched packets to be dropped. > > + Otherwise, the column should contain a sequence of actions, each > > + terminated by a semicolon. > > + </p> > > + > > + <p> > > + Please see the documentation of the > > + <ref > > + column="actions" table="Logical_Flow" db="OVN_Southbound"/> > > + column of the <ref table="Logical_Flow" > > + db="OVN_Southbound"/> table in the <ref db="OVN_Southbound"/> database. > > + </p> > > + </column> > > + > > + <column name="external_ids"> > > + See <em>External IDs</em> at the beginning of this document. > > + </column> > > + </table> > > +</database> > > diff --git a/tutorial/ovn-sandbox b/tutorial/ovn-sandbox > > index ed334d1c31..239db58f35 100755 > > --- a/tutorial/ovn-sandbox > > +++ b/tutorial/ovn-sandbox > > @@ -64,6 +64,8 @@ gdb_ovn_controller=false > > gdb_ovn_controller_ex=false > > gdb_ovn_controller_vtep=false > > gdb_ovn_controller_vtep_ex=false > > +gdb_ovn_br_controller=false > > +gdb_ovn_br_controller_ex=false > > builddir= > > ovsbuilddir= > > srcdir= > > @@ -90,6 +92,7 @@ ic_nb_servers=3 > > ic_sb_model=clustered > > ic_sb_servers=3 > > dummy=override > > +ovn_br=false > > > > for option; do > > # This option-parsing mechanism borrowed from a Autoconf-generated > > @@ -339,6 +342,9 @@ EOF > > --ic-sb-m*) > > prev=ic_sb_model > > ;; > > + --ovn-br) > > + ovn_br=true > > + ;; > > -R|--gdb-run) > > gdb_vswitchd_ex=true > > gdb_ovsdb_ex=true > > @@ -422,6 +428,11 @@ if $built; then > > echo >&2 'source directory not found, please use --srcdir' > > exit 1 > > fi > > + br_schema=$srcdir/ovn-br.ovsschema > > + if test ! -e "$br_schema"; then > > + echo >&2 'source directory not found, please use --srcdir' > > + exit 1 > > + fi > > > > # Put built tools early in $PATH. > > if test ! -e $ovsbuilddir/vswitchd/ovs-vswitchd; then > > @@ -429,7 +440,7 @@ if $built; then > > exit 1 > > fi > > PATH=$ovsbuilddir/ovsdb:$ovsbuilddir/vswitchd:$ovsbuilddir/utilities:$ovsbuilddir/vtep:$PATH > > - PATH=$builddir/controller:$builddir/controller-vtep:$builddir/northd:$builddir/ic:$builddir/utilities:$PATH > > + PATH=$builddir/controller:$builddir/controller-vtep:$builddir/northd:$builddir/ic:$builddir/utilities:$builddir/br-controller:$PATH > > export PATH > > else > > case $schema in > > @@ -616,6 +627,10 @@ ovn_start_db sb "$sbdb_model" "$sbdb_servers" "$ovnsb_source" > > ovn_start_db ic_nb "$ic_nb_model" "$ic_nb_servers" "$ic_nb_schema" > > ovn_start_db ic_sb "$ic_sb_model" "$ic_sb_servers" "$ic_sb_schema" > > > > +if $ovn_br; then > > + ovn_start_db br standalone 1 "$br_schema" > > +fi > > + > > #Add a small delay to allow ovsdb-server to launch. > > sleep 0.1 > > > > @@ -692,6 +707,13 @@ rungdb $gdb_ovn_controller_vtep $gdb_ovn_controller_vtep_ex \ > > $OVN_CTRLR_PKI --log-file -vsyslog:off \ > > --ovnsb-db="$OVN_SB_DB" > > > > +if $ovn_br; then > > + run ovs-vsctl set open . external-ids:ovn-br-remote=$OVN_BR_DB > > + rungdb $gdb_ovn_br_controller $gdb_ovn_br_controller_ex ovn-br-controller \ > > + --detach --no-chdir -vsyslog:off \ > > + --log-file=ovn-br-controller.log \ > > + --pidfile=ovn-br-controller.pid -vconsole:off > > +fi > > cat <<EOF > > > > > > diff --git a/utilities/automake.mk b/utilities/automake.mk > > index 1de33614f9..3e3f2e08c9 100644 > > --- a/utilities/automake.mk > > +++ b/utilities/automake.mk > > @@ -119,4 +119,12 @@ bin_PROGRAMS += utilities/ovn-debug > > utilities_ovn_debug_SOURCES = utilities/ovn-debug.c > > utilities_ovn_debug_LDADD = lib/libovn.la $(OVSDB_LIBDIR)/libovsdb.la $(OVS_LIBDIR)/libopenvswitch.la > > > > +# ovn-brctl > > +bin_PROGRAMS += utilities/ovn-brctl > > +utilities_ovn_brctl_SOURCES = \ > > + utilities/ovn-dbctl.c \ > > + utilities/ovn-dbctl.h \ > > + utilities/ovn-brctl.c > > +utilities_ovn_brctl_LDADD = lib/libovn.la $(OVSDB_LIBDIR)/libovsdb.la $(OVS_LIBDIR)/libopenvswitch.la > > + > > include utilities/bugtool/automake.mk > > diff --git a/utilities/ovn-brctl.c b/utilities/ovn-brctl.c > > new file mode 100644 > > index 0000000000..051a897748 > > --- /dev/null > > +++ b/utilities/ovn-brctl.c > > @@ -0,0 +1,585 @@ > > +/* > > + * Licensed under the Apache License, Version 2.0 (the "License"); > > + * you may not use this file except in compliance with the License. > > + * You may obtain a copy of the License at: > > + * > > + * http://www.apache.org/licenses/LICENSE-2.0 > > + * > > + * Unless required by applicable law or agreed to in writing, software > > + * distributed under the License is distributed on an "AS IS" BASIS, > > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. > > + * See the License for the specific language governing permissions and > > + * limitations under the License. > > + */ > > + > > + #include <config.h> > > + > > +#include <getopt.h> > > +#include <inttypes.h> > > +#include <stdlib.h> > > +#include <stdio.h> > > + > > +/* OVS includes. */ > > +#include "command-line.h" > > +#include "daemon.h" > > +#include "db-ctl-base.h" > > +#include "dirs.h" > > +#include "openvswitch/vlog.h" > > + > > +/* OVN includes. */ > > +#include "lib/ovn-br-idl.h" > > +#include "lib/ovn-dirs.h" > > +#include "lib/ovn-util.h" > > +#include "lib/vec.h" > > +#include "ovn-dbctl.h" > > + > > +VLOG_DEFINE_THIS_MODULE(brctl); > > + > > +static char * OVS_WARN_UNUSED_RESULT br_by_name_or_uuid( > > + struct ctl_context *ctx, const char *id, bool must_exist, > > + const struct ovnbrrec_bridge **br_p); > > + > > +static void > > +brctl_add_base_prerequisites(struct ovsdb_idl *idl, > > + enum nbctl_wait_type wait_type OVS_UNUSED) > > +{ > > + ovsdb_idl_add_table(idl, &ovnbrrec_table_br_global); > > +} > > + > > +static void > > +brctl_pre_execute(struct ovsdb_idl *idl, struct ovsdb_idl_txn *txn, > > + enum nbctl_wait_type wait_type OVS_UNUSED) > > +{ > > + const struct ovnbrrec_br_global *br = ovnbrrec_br_global_first(idl); > > + if (!br) { > > + br = ovnbrrec_br_global_insert(txn); > > + } > > +} > > + > > +static int > > +get_inactivity_probe(struct ovsdb_idl *idl) > > +{ > > + const struct ovnbrrec_br_global *pr = ovnbrrec_br_global_first(idl); > > + int interval = DEFAULT_UTILS_PROBE_INTERVAL_MSEC; > > + > > + if (pr) { > > + interval = smap_get_int(&pr->options, "brctl_probe_interval", > > + interval); > > + } > > + > > + return interval; > > +} > > + > > +/* ovn-brctl specific context. Inherits the 'struct ctl_context' as base. */ > > +struct brctl_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 > > The comment references sbctl_ functions instead of brctl_ functions. > However, neither sbctl_context_populate_cache() nor > brctl_context_populate_cache() actually exist. In the case of ovn-sbctl, > it maintains a cache of some objects, but ovn-brctl is not doing that. I > think that you can remove the cache_valid field altogether. Ack. I'll address in v2. > > > + * maintain its correctness. */ > > + bool cache_valid; > > +}; > > + > > +/* Casts 'base' into 'struct sbctl_context'. */ > > +static struct brctl_context * > > +brctl_context_cast(struct ctl_context *base) > > +{ > > + return CONTAINER_OF(base, struct brctl_context, base); > > +} > > + > > +static struct ctl_context * > > +brctl_ctx_create(void) > > +{ > > + struct brctl_context *prctx = xmalloc(sizeof *prctx); > > + *prctx = (struct brctl_context) { > > + .cache_valid = false, > > + }; > > + return &prctx->base; > > +} > > + > > +static void > > +brctl_context_invalidate_cache(struct ctl_context *ctx) > > +{ > > + struct brctl_context *brctl_ctx = brctl_context_cast(ctx); > > + > > + if (!brctl_ctx->cache_valid) { > > + return; > > + } > > + brctl_ctx->cache_valid = false; > > +} > > + > > +static void > > +brctl_ctx_destroy(struct ctl_context *ctx) > > +{ > > + brctl_context_invalidate_cache(ctx); > > + free(ctx); > > +} > > + > > +static void > > +print_br(const struct ovnbrrec_bridge *br, struct ds *s) > > +{ > > + ds_put_format(s, "bridge "UUID_FMT" (%s)\n", > > + UUID_ARGS(&br->header_.uuid), br->name); > > +} > > + > > +static void > > +brctl_init(struct ctl_context *ctx OVS_UNUSED) > > +{ > > +} > > + > > +static void > > +brctl_pre_show(struct ctl_context *ctx) > > +{ > > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_name); > > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_options); > > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_external_ids); > > +} > > + > > +static const struct ctl_table_class tables[OVNBRREC_N_TABLES] = { > > + [OVNBRREC_TABLE_BRIDGE].row_ids[0] > > + = {&ovnbrrec_bridge_col_name, NULL, NULL}, > > + > > + [OVNBRREC_TABLE_CONNECTION].row_ids[0] > > + = {&ovnbrrec_connection_col_target, NULL, NULL}, > > +}; > > + > > +static void > > +brctl_show(struct ctl_context *ctx) > > +{ > > + const struct ovnbrrec_bridge *br; > > + > > + if (ctx->argc == 2) { > > + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &br); > > + if (error) { > > + ctx->error = error; > > + return; > > + } > > + > > + print_br(br, &ctx->output); > > + } else { > > + OVNBRREC_BRIDGE_FOR_EACH (br, ctx->idl) { > > + print_br(br, &ctx->output); > > + } > > + } > > +} > > + > > +static void > > +pre_get_info(struct ctl_context *ctx) > > +{ > > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_name); > > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_options); > > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_external_ids); > > + > > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_actions); > > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_bridge); > > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_match); > > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_priority); > > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_table_id); > > + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_external_ids); > > +} > > + > > +static bool > > +is_uuid_with_prefix(const char *uuid) > > +{ > > + return uuid[0] == '0' && (uuid[1] == 'x' || uuid[1] == 'X'); > > +} > > + > > +static const char * > > +strip_leading_zero(const char *s) > > +{ > > + return s + strspn(s, "0"); > > +} > > + > > +static bool > > +is_partial_uuid_match(const struct uuid *uuid, const char *match) > > +{ > > + char uuid_s[UUID_LEN + 1]; > > + snprintf(uuid_s, sizeof uuid_s, UUID_FMT, UUID_ARGS(uuid)); > > + > > + /* We strip leading zeros because we want to accept cookie values derived > > + * from UUIDs, and cookie values are printed without leading zeros because > > + * they're just numbers. */ > > + const char *s1 = strip_leading_zero(uuid_s); > > + const char *s2 = match; > > + if (is_uuid_with_prefix(s2)) { > > + s2 = s2 + 2; > > + } > > + s2 = strip_leading_zero(s2); > > + return !strncmp(s1, s2, strlen(s2)); > > +} > > strip_leading_zero() and is_partial_uuid_match() are ripped straight > from ovn-sbctl.c . These should probably be placed in ovn-util so that > both utilities can reference the same functions. Ack. I'll address in v2. > > > + > > + > > +struct brctl_lflow { > > + const struct ovnbrrec_logical_flow *lflow; > > + const struct ovnbrrec_bridge *br; > > +}; > > + > > +static int > > +brctl_lflow_cmp(const void *a_, const void *b_) > > +{ > > + const struct brctl_lflow *a_ctl_lflow = a_; > > + const struct brctl_lflow *b_ctl_lflow = b_; > > + > > + const struct ovnbrrec_logical_flow *a = a_ctl_lflow->lflow; > > + const struct ovnbrrec_logical_flow *b = b_ctl_lflow->lflow; > > + > > + const struct ovnbrrec_bridge *abr = a_ctl_lflow->br; > > + const struct ovnbrrec_bridge *bbr = b_ctl_lflow->br; > > + const char *a_name = abr->name; > > + const char *b_name = bbr->name; > > + int cmp = strcmp(a_name, b_name); > > + if (cmp) { > > + return cmp; > > + } > > + > > + cmp = uuid_compare_3way(&abr->header_.uuid, &bbr->header_.uuid); > > + if (cmp) { > > + return cmp; > > + } > > + > > + cmp = (a->table_id > b->table_id ? 1 > > + : a->table_id < b->table_id ? -1 > > + : a->priority > b->priority ? -1 > > + : a->priority < b->priority ? 1 > > + : strcmp(a->match, b->match)); > > + return cmp ? cmp : strcmp(a->actions, b->actions); > > +} > > + > > +static void > > +brctl_lflow_add(struct vector *lflows, > > + const struct ovnbrrec_logical_flow *lflow, > > + const struct ovnbrrec_bridge *br) > > +{ > > + struct brctl_lflow brctl_lflow = (struct brctl_lflow) { > > + .lflow = lflow, > > + .br = br, > > + }; > > + vector_push(lflows, &brctl_lflow); > > +} > > + > > +static void > > +print_uuid_part(const struct uuid *uuid, bool do_print, struct ds *s) > > +{ > > + if (!do_print) { > > + return; > > + } > > + ds_put_format(s, "uuid=0x%08"PRIx32", ", uuid->parts[0]); > > +} > > + > > +static void > > +print_bridge_prompt(const struct ovnbrrec_bridge *br, > > + const struct uuid *uuid, struct ds *s) { > > + ds_put_format(s, "Bridge: %s", br->name); > > + ds_put_format(s, " ("UUID_FMT")\n", > > + UUID_ARGS(uuid)); > > +} > > + > > +static char * OVS_WARN_UNUSED_RESULT > > +parse_priority(const char *arg, int64_t *priority_p) > > +{ > > + /* Validate priority. */ > > + int64_t priority; > > + if (!ovs_scan(arg, "%"SCNd64, &priority) > > + || priority < 0 || priority > 32767) { > > + /* Priority_p could be uninitialized as no valid priority was > > + * input, initialize it to a valid value of 0 before returning */ > > + *priority_p = 0; > > + return xasprintf("%s: priority must in range 0...32767", arg); > > + } > > + *priority_p = priority; > > + return NULL; > > +} > > + > > +static char * OVS_WARN_UNUSED_RESULT > > +parse_table_id(const char *arg, int64_t *table_p) > > +{ > > + /* Validate table id. */ > > + int64_t table; > > + if (!ovs_scan(arg, "%"SCNd64, &table) > > + || table < 0 || table > 55) { > > + /* table_p could be uninitialized as no valid table id was > > + * input, initialize it to a valid value of 0 before returning */ > > + *table_p = 0; > > + return xasprintf("%s: table must in range 0...55", arg); > > + } > > + *table_p = table; > > + return NULL; > > +} > > + > > +static char * OVS_WARN_UNUSED_RESULT > > +br_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist, > > + const struct ovnbrrec_bridge **br_p) > > +{ > > + const struct ovsdb_idl_row *row = NULL; > > + > > + char *error = ctl_get_row(ctx, &ovnbrrec_table_bridge, > > + id, must_exist, &row); > > + *br_p = (const struct ovnbrrec_bridge *) row; > > + return error; > > +} > > + > > +static void > > +cmd_br_add(struct ctl_context *ctx) > > +{ > > + const struct ovnbrrec_bridge *bridge = NULL; > > + > > + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], false, &bridge); > > + if (error) { > > + ctl_error(ctx, "%s", error); > > + free(error); > > + return; > > + } > > + > > + if (bridge) { > > + ctl_error(ctx, "Bridge %s already exists", ctx->argv[1]); > > + } > > + > > + bridge = ovnbrrec_bridge_insert(ctx->txn); > > + ovnbrrec_bridge_set_name(bridge, ctx->argv[1]); > > +} > > + > > +static void > > +cmd_br_del(struct ctl_context *ctx) > > +{ > > + const struct ovnbrrec_bridge *bridge = NULL; > > + > > + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &bridge); > > + if (error) { > > + ctl_error(ctx, "%s", error); > > + free(error); > > + return; > > + } > > + > > + ovnbrrec_bridge_delete(bridge); > > +} > > + > > +static void > > +cmd_lflow_list(struct ctl_context *ctx) > > +{ > > + struct vector lflows = VECTOR_EMPTY_INITIALIZER(struct brctl_lflow); > > + const struct ovnbrrec_bridge *bridge = NULL; > > + > > + if (ctx->argc > 1) { > > + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], false, &bridge); > > + if (error) { > > + ctl_error(ctx, "%s", error); > > + free(error); > > + return; > > + } > > + > > + if (bridge) { > > + ctx->argc--; > > + ctx->argv++; > > + } > > + } > > + > > + const struct ovnbrrec_logical_flow *lflow; > > + OVNBRREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->idl) { > > + if (bridge && lflow->bridge != bridge) { > > + continue; > > + } > > + > > + brctl_lflow_add(&lflows, lflow, lflow->bridge); > > + } > > + > > + vector_qsort(&lflows, brctl_lflow_cmp); > > + > > + bool print_uuid = shash_find(&ctx->options, "--uuid") != NULL; > > + > > + const struct brctl_lflow *curr, *prev = NULL; > > + VECTOR_FOR_EACH_PTR (&lflows, curr) { > > + /* Figure out whether to print this particular flow. By default, we > > + * print all flows, but if any UUIDs were listed on the command line > > + * then we only print the matching ones. */ > > + bool include; > > + if (ctx->argc > 1) { > > + include = false; > > + for (size_t j = 1; j < ctx->argc; j++) { > > + if (is_partial_uuid_match(&curr->lflow->header_.uuid, > > + ctx->argv[j])) { > > + include = true; > > + break; > > + } > > + } > > + } else { > > + include = true; > > + } > > + if (!include) { > > + continue; > > + } > > + > > + /* Print a header line for this datapath or pipeline, if we haven't > > + * already done so. */ > > + if (!prev || prev->br != curr->br) { > > + print_bridge_prompt(curr->br, &curr->br->header_.uuid, > > + &ctx->output); > > + } > > + > > + /* Print the flow. */ > > + ds_put_cstr(&ctx->output, " "); > > + print_uuid_part(&curr->lflow->header_.uuid, print_uuid, &ctx->output); > > + ds_put_format(&ctx->output, > > + "table=%-2"PRId64", priority=%-5"PRId64 > > + ", match=(%s), action=(%s)\n", > > + curr->lflow->table_id, > > + curr->lflow->priority, curr->lflow->match, > > + curr->lflow->actions); > > + prev = curr; > > + } > > + > > + vector_destroy(&lflows); > > +} > > + > > +static void > > +cmd_lflow_add(struct ctl_context *ctx) > > +{ > > + const struct ovnbrrec_bridge *bridge = NULL; > > + > > + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &bridge); > > + if (error) { > > + ctl_error(ctx, "%s", error); > > + free(error); > > + return; > > + } > > + > > + int64_t table_id; > > + error = parse_table_id(ctx->argv[2], &table_id); > > + if (error) { > > + ctx->error = error; > > + return; > > + } > > + > > + int64_t priority; > > + error = parse_priority(ctx->argv[3], &priority); > > + if (error) { > > + ctx->error = error; > > + return; > > + } > > + > > + struct ovnbrrec_logical_flow *lflow = > > + ovnbrrec_logical_flow_insert(ctx->txn); > > + ovnbrrec_logical_flow_set_bridge(lflow, bridge); > > + ovnbrrec_logical_flow_set_table_id(lflow, table_id); > > + ovnbrrec_logical_flow_set_priority(lflow, priority); > > + ovnbrrec_logical_flow_set_match(lflow, ctx->argv[4]); > > + ovnbrrec_logical_flow_set_actions(lflow, ctx->argv[5]); > > +} > > + > > +static void > > +cmd_lflow_del(struct ctl_context *ctx) > > +{ > > + const struct ovnbrrec_logical_flow *lflow; > > + const struct ovsdb_idl_row *row; > > + > > + char *error = ctl_get_row(ctx, &ovnbrrec_table_logical_flow, > > + ctx->argv[1], true, &row); > > + if (error) { > > + ctl_error(ctx, "%s", error); > > + free(error); > > + return; > > + } > > + > > + lflow = (const struct ovnbrrec_logical_flow *) row; > > + ovnbrrec_logical_flow_delete(lflow); > > +} > > + > > +static void > > +cmd_lflows_del(struct ctl_context *ctx) > > +{ > > + const struct ovnbrrec_bridge *bridge = NULL; > > + > > + if (ctx->argc > 1) { > > + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &bridge); > > + if (error) { > > + ctl_error(ctx, "%s", error); > > + free(error); > > + return; > > + } > > + } > > + > > + const struct ovnbrrec_logical_flow *lflow; > > + OVNBRREC_LOGICAL_FLOW_FOR_EACH_SAFE (lflow, ctx->idl) { > > + if (!bridge || lflow->bridge == bridge) { > > + ovnbrrec_logical_flow_delete(lflow); > > + } > > + } > > +} > > + > > +static const struct ctl_command_syntax brctl_commands[] = { > > + { "init", 0, 0, "", NULL, brctl_init, NULL, "", RW }, > > + { "show", 0, 1, "[BRIDGE]", brctl_pre_show, brctl_show, NULL, "", RO }, > > + > > + /* Bridge commands. */ > > + {"add-br", 1, 1, "BRIDGE", pre_get_info, cmd_br_add, NULL, > > + "", RW}, > > + {"del-br", 1, 1, "BRIDGE", pre_get_info, cmd_br_del, NULL, > > + "", RW}, > > + > > + /* Logical flow commands */ > > + {"lflow-list", 0, INT_MAX, "[BRIDGE] [LFLOW...]", > > + pre_get_info, cmd_lflow_list, NULL, > > + "--uuid,--ovs?,--stats,--vflows?", RO}, > > + {"dump-flows", 0, INT_MAX, "[DATAPATH] [LFLOW...]", > > + pre_get_info, cmd_lflow_list, NULL, > > + "--uuid,--ovs?,--stats,--vflows?", > > + RO}, /* Friendly alias for lflow-list */ > > + {"add-flow", 5, 5, "BRIDGE TABLE PRIORITY MATCH ACTION", > > + pre_get_info, cmd_lflow_add, NULL, > > + "", RW}, > > + {"del-flow", 1, 1, "UUID", pre_get_info, cmd_lflow_del, NULL, > > + "", RW}, > > + {"del-flows", 0, 1, "[BRIDGE]", pre_get_info, cmd_lflows_del, NULL, > > + "", RW}, > > + {NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, RO}, > > +}; > > + > > +static void > > +brctl_usage(void) > > +{ > > + printf("\ > > +%s: OVN Provider DB management utility\n\ > > +usage: %s [OPTIONS] COMMAND [ARG...]\n\ > > +\n\ > > +General commands:\n\ > > + init initialize the database\n\ > > + show print overview of database contents\n\ > > +\n\ > > +Logical flow commands:\n\ > > + lflow-list [BRIDGE] [LFLOW...] list logical flows for BRIDGE\n\ > > + dump-flows [BRIDGE] [LFLOW...] alias for lflow-list\n\ > > +\n\ > > +\n\n", program_name, program_name); > > +} > > + > > +int > > +main(int argc, char *argv[]) > > +{ > > + struct ovn_dbctl_options dbctl_options = { > > + .db_version = ovnbrrec_get_db_version(), > > + .default_db = default_br_db(), > > + .allow_wait = false, > > + > > + .options_env_var_name = "OVN_brctl_OPTIONS", > > + .daemon_env_var_name = "OVN_PR_DAEMON", > > + > > + .idl_class = &ovnbrrec_idl_class, > > + .tables = tables, > > + .cmd_show_table = NULL, > > + .commands = brctl_commands, > > + > > + .usage = brctl_usage, > > + .add_base_prerequisites = brctl_add_base_prerequisites, > > + .pre_execute = brctl_pre_execute, > > + .post_execute = NULL, > > + .get_inactivity_probe = get_inactivity_probe, > > + > > + .ctx_create = brctl_ctx_create, > > + .ctx_destroy = brctl_ctx_destroy, > > + }; > > + > > + return ovn_dbctl_main(argc, argv, &dbctl_options); > > +} >
diff --git a/Makefile.am b/Makefile.am index ea98fb5fb5..1936e9697a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -115,7 +115,9 @@ EXTRA_DIST = \ ovn-ic-nb.ovsschema \ ovn-ic-nb.xml \ ovn-ic-sb.ovsschema \ - ovn-ic-sb.xml + ovn-ic-sb.xml \ + ovn-br.ovsschema \ + ovn-br.xml bin_PROGRAMS = sbin_PROGRAMS = bin_SCRIPTS = @@ -507,4 +509,5 @@ include controller/automake.mk include controller-vtep/automake.mk include northd/automake.mk include ic/automake.mk +include br-controller/automake.mk include build-aux/automake.mk diff --git a/automake.mk b/automake.mk index a7947a3f54..53860ad89b 100644 --- a/automake.mk +++ b/automake.mk @@ -120,6 +120,35 @@ ovn-ic-sb.5: \ $(srcdir)/ovn-ic-sb.xml > $@.tmp && \ mv $@.tmp $@ +# OVN bridge controller E-R diagram +# +# If "python" or "dot" is not available, then we do not add graphical diagram +# to the documentation. +if HAVE_DOT +ovn-br.gv: ${OVSDIR}/ovsdb/ovsdb-dot.in $(srcdir)/ovn-br.ovsschema + $(AM_V_GEN)$(OVSDB_DOT) --no-arrows $(srcdir)/ovn-br.ovsschema > $@ +ovn-br.pic: ovn-br.gv ${OVSDIR}/ovsdb/dot2pic + $(AM_V_GEN)(dot -T plain < ovn-br.gv | $(PYTHON3) ${OVSDIR}/ovsdb/dot2pic -f 3) > $@.tmp && \ + mv $@.tmp $@ +OVN_BR_PIC = ovn-br.pic +OVN_BR_DOT_DIAGRAM_ARG = --er-diagram=$(OVN_BR_PIC) +CLEANFILES += ovn-br.gv ovn-br.pic +endif + +# OVN bridge controller schema documentation +EXTRA_DIST += ovn-br.xml +CLEANFILES += ovn-br.5 +man_MANS += ovn-br.5 + +ovn-br.5: \ + ${OVSDIR}/ovsdb/ovsdb-doc $(srcdir)/ovn-br.xml $(srcdir)/ovn-br.ovsschema $(OVN_BR_PIC) + $(AM_V_GEN)$(OVSDB_DOC) \ + $(OVN_BR_DOT_DIAGRAM_ARG) \ + --version=$(VERSION) \ + $(srcdir)/ovn-br.ovsschema \ + $(srcdir)/ovn-br.xml > $@.tmp && \ + mv $@.tmp $@ + # Version checking for ovn-nb.ovsschema. ALL_LOCAL += ovn-nb.ovsschema.stamp ovn-nb.ovsschema.stamp: ovn-nb.ovsschema @@ -143,9 +172,16 @@ ovn-ic-sb.ovsschema.stamp: ovn-ic-sb.ovsschema $(srcdir)/build-aux/cksum-schema-check $? $@ CLEANFILES += ovn-ic-sb.ovsschema.stamp +# Version checking for ovn-br.ovsschema. +ALL_LOCAL += ovn-br.ovsschema.stamp +ovn-br.ovsschema.stamp: ovn-br.ovsschema + $(srcdir)/build-aux/cksum-schema-check $? $@ +CLEANFILES += ovn-br.ovsschema.stamp + pkgdata_DATA += ovn-nb.ovsschema pkgdata_DATA += ovn-sb.ovsschema pkgdata_DATA += ovn-ic-nb.ovsschema pkgdata_DATA += ovn-ic-sb.ovsschema +pkgdata_DATA += ovn-br.ovsschema CLEANFILES += ovn-sb.ovsschema.stamp diff --git a/br-controller/automake.mk b/br-controller/automake.mk new file mode 100644 index 0000000000..012e9a5ed8 --- /dev/null +++ b/br-controller/automake.mk @@ -0,0 +1,8 @@ +bin_PROGRAMS += br-controller/ovn-br-controller +br_controller_ovn_br_controller_SOURCES = \ + br-controller/ovn-br-controller.c + +br_controller_ovn_br_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la +man_MANS += br-controller/ovn-br-controller.8 +EXTRA_DIST += br-controller/ovn-br-controller.8.xml +CLEANFILES += br-controller/ovn-br-controller.8 diff --git a/br-controller/ovn-br-controller.8.xml b/br-controller/ovn-br-controller.8.xml new file mode 100644 index 0000000000..fa6eb2e2e2 --- /dev/null +++ b/br-controller/ovn-br-controller.8.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<manpage program="ovn-br-controller" section="8" title="ovn-br-controller"> + <h1>Name</h1> + <p>ovn-br-controller -- Open Virtual Network local OVS bridge controller</p> +</manpage> diff --git a/br-controller/ovn-br-controller.c b/br-controller/ovn-br-controller.c new file mode 100644 index 0000000000..0fef0e5fee --- /dev/null +++ b/br-controller/ovn-br-controller.c @@ -0,0 +1,175 @@ +/* Copyright (c) 2025 Crusoe Energy Systems LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> + +#include <errno.h> +#include <getopt.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> + +/* OVS includes. */ +#include "openvswitch/vlog.h" +#include "lib/command-line.h" +#include "lib/daemon.h" +#include "lib/dirs.h" +#include "lib/fatal-signal.h" +#include "lib/stream.h" +#include "lib/stream-ssl.h" +#include "lib/unixctl.h" + +/* OVN includes. */ +#include "lib/ovn-br-idl.h" +#include "lib/ovn-util.h" + +VLOG_DEFINE_THIS_MODULE(main); + +static void parse_options(int argc, char *argv[]); +OVS_NO_RETURN static void usage(void); + + +/* SSL/TLS options. */ +static const char *ssl_private_key_file; +static const char *ssl_certificate_file; +static const char *ssl_ca_cert_file; + +/* --unixctl-path: Path to use for unixctl server socket. */ +static char *unixctl_path; + +int +main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + ovs_cmdl_proctitle_init(argc, argv); + ovn_set_program_name(argv[0]); + service_start(&argc, &argv); + parse_options(argc, argv); + fatal_ignore_sigpipe(); + + return 0; +} + +/* static functions. */ +static void +parse_options(int argc, char *argv[]) +{ + enum { + OPT_PEER_CA_CERT = UCHAR_MAX + 1, + OPT_BOOTSTRAP_CA_CERT, + VLOG_OPTION_ENUMS, + OVN_DAEMON_OPTION_ENUMS, + SSL_OPTION_ENUMS, + }; + + static struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {"unixctl", required_argument, NULL, 'u'}, + VLOG_LONG_OPTIONS, + OVN_DAEMON_LONG_OPTIONS, + STREAM_SSL_LONG_OPTIONS, + {"peer-ca-cert", required_argument, NULL, OPT_PEER_CA_CERT}, + {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT}, + {NULL, 0, NULL, 0} + }; + char *short_options = ovs_cmdl_long_options_to_short_options(long_options); + + for (;;) { + int c; + + c = getopt_long(argc, argv, short_options, long_options, NULL); + if (c == -1) { + break; + } + + switch (c) { + case 'h': + usage(); + + case 'V': + ovs_print_version(OFP15_VERSION, OFP15_VERSION); + printf("OVN BR DB Schema %s\n", ovnbrrec_get_db_version()); + exit(EXIT_SUCCESS); + + case 'u': + unixctl_path = optarg; + break; + + VLOG_OPTION_HANDLERS + OVN_DAEMON_OPTION_HANDLERS + + case 'p': + ssl_private_key_file = optarg; + break; + + case 'c': + ssl_certificate_file = optarg; + break; + + case 'C': + ssl_ca_cert_file = optarg; + break; + + case OPT_SSL_PROTOCOLS: + stream_ssl_set_protocols(optarg); + break; + + case OPT_SSL_CIPHERS: + stream_ssl_set_ciphers(optarg); + break; + + case OPT_SSL_CIPHERSUITES: + stream_ssl_set_ciphersuites(optarg); + break; + + case OPT_PEER_CA_CERT: + stream_ssl_set_peer_ca_cert_file(optarg); + break; + + case OPT_BOOTSTRAP_CA_CERT: + stream_ssl_set_ca_cert_file(optarg, true); + break; + + case '?': + exit(EXIT_FAILURE); + + default: + ovs_abort(0, "Invalid option."); + } + } + free(short_options); + + argc -= optind; + argv += optind; +} + +static void +usage(void) +{ + printf("%s: OVN bridge controller\n" + "usage %s [OPTIONS] [OVS-DATABASE]\n" + "where OVS-DATABASE is a socket on which the OVS OVSDB server " + "is listening.\n", + program_name, program_name); + stream_usage("OVS-DATABASE", true, false, true); + daemon_usage(); + vlog_usage(); + printf("\nOther options:\n" + " -u, --unixctl=SOCKET set control socket name\n" + " -n custom chassis name\n" + " -h, --help display this help message\n" + " -V, --version display version information\n"); + exit(EXIT_SUCCESS); +} diff --git a/lib/automake.mk b/lib/automake.mk index a59c722d62..46c24e99d1 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -59,7 +59,9 @@ nodist_lib_libovn_la_SOURCES = \ lib/ovn-ic-nb-idl.c \ lib/ovn-ic-nb-idl.h \ lib/ovn-ic-sb-idl.c \ - lib/ovn-ic-sb-idl.h + lib/ovn-ic-sb-idl.h \ + lib/ovn-br-idl.c \ + lib/ovn-br-idl.h CLEANFILES += $(nodist_lib_libovn_la_SOURCES) @@ -129,3 +131,16 @@ OVN_IC_SB_IDL_FILES = \ lib/ovn-ic-sb-idl.ovsidl: $(OVN_IC_SB_IDL_FILES) $(AM_V_GEN)$(OVSDB_IDLC) annotate $(OVN_IC_SB_IDL_FILES) > $@.tmp && \ mv $@.tmp $@ + +# ovn-br IDL +OVSIDL_BUILT += \ + lib/ovn-br-idl.c \ + lib/ovn-br-idl.h \ + lib/ovn-br-idl.ovsidl +EXTRA_DIST += lib/ovn-br-idl.ann +OVN_PR_IDL_FILES = \ + $(srcdir)/ovn-br.ovsschema \ + $(srcdir)/lib/ovn-br-idl.ann +lib/ovn-br-idl.ovsidl: $(OVN_PR_IDL_FILES) + $(AM_V_GEN)$(OVSDB_IDLC) annotate $(OVN_PR_IDL_FILES) > $@.tmp && \ + mv $@.tmp $@ diff --git a/lib/ovn-br-idl.ann b/lib/ovn-br-idl.ann new file mode 100644 index 0000000000..80993338a6 --- /dev/null +++ b/lib/ovn-br-idl.ann @@ -0,0 +1,9 @@ +# -*- python -*- + +# This code, when invoked by "ovsdb-idlc annotate" (by the build +# process), annotates ovn-br.ovsschema with additional data that give +# the ovsdb-idl engine information about the types involved, so that +# it can generate more programmer-friendly data structures. + +s["idlPrefix"] = "ovnbrrec_" +s["idlHeader"] = "\"lib/ovn-br-idl.h\"" diff --git a/lib/ovn-util.c b/lib/ovn-util.c index ab5c4fc477..1cfeed6044 100644 --- a/lib/ovn-util.c +++ b/lib/ovn-util.c @@ -560,6 +560,19 @@ default_ic_sb_db(void) return def; } +const char * +default_br_db(void) +{ + static char *def; + if (!def) { + def = getenv("OVN_BR_DB"); + if (!def) { + def = xasprintf("unix:%s/ovnbr_db.sock", ovn_rundir()); + } + } + return def; +} + char * get_abs_unix_ctl_path(const char *path) { diff --git a/lib/ovn-util.h b/lib/ovn-util.h index bde40666a1..c66d7c31ff 100644 --- a/lib/ovn-util.h +++ b/lib/ovn-util.h @@ -131,6 +131,7 @@ const char *default_nb_db(void); const char *default_sb_db(void); const char *default_ic_nb_db(void); const char *default_ic_sb_db(void); +const char *default_br_db(void); char *get_abs_unix_ctl_path(const char *path); struct ovsdb_idl_table_class; diff --git a/ovn-br.ovsschema b/ovn-br.ovsschema new file mode 100644 index 0000000000..5635ee5bbd --- /dev/null +++ b/ovn-br.ovsschema @@ -0,0 +1,94 @@ +{ + "name": "OVN_Bridge_Controller", + "version": "0.0.1", + "cksum": "124113656 4389", + "tables": { + "BR_Global": { + "columns": { + "br_cfg": {"type": {"key": "integer"}}, + "external_ids": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}, + "connections": { + "type": {"key": {"type": "uuid", + "refTable": "Connection"}, + "min": 0, + "max": "unlimited"}}, + "ssl": { + "type": {"key": {"type": "uuid", + "refTable": "SSL"}, + "min": 0, "max": 1}}, + "options": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}}, + "maxRows": 1, + "isRoot": true}, + "Connection": { + "columns": { + "target": {"type": "string"}, + "max_backoff": {"type": {"key": {"type": "integer", + "minInteger": 1000}, + "min": 0, + "max": 1}}, + "inactivity_probe": {"type": {"key": "integer", + "min": 0, + "max": 1}}, + "other_config": {"type": {"key": "string", + "value": "string", + "min": 0, + "max": "unlimited"}}, + "external_ids": {"type": {"key": "string", + "value": "string", + "min": 0, + "max": "unlimited"}}, + "is_connected": {"type": "boolean", "ephemeral": true}, + "status": {"type": {"key": "string", + "value": "string", + "min": 0, + "max": "unlimited"}, + "ephemeral": true}}, + "indexes": [["target"]]}, + "SSL": { + "columns": { + "private_key": {"type": "string"}, + "certificate": {"type": "string"}, + "ca_cert": {"type": "string"}, + "bootstrap_ca_cert": {"type": "boolean"}, + "ssl_protocols": {"type": "string"}, + "ssl_ciphers": {"type": "string"}, + "ssl_ciphersuites": {"type": "string"}, + "external_ids": {"type": {"key": "string", + "value": "string", + "min": 0, + "max": "unlimited"}}}, + "maxRows": 1}, + "Bridge": { + "columns": { + "name": {"type": "string"}, + "options": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}, + "external_ids": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}}, + "indexes": [["name"]], + "isRoot": true}, + "Logical_Flow": { + "columns": { + "bridge": {"type": {"key": {"type": "uuid", + "refTable": "Bridge"}, + "min": 0, "max": 1}}, + "table_id": {"type": {"key": {"type": "integer", + "minInteger": 0, + "maxInteger": 32}}}, + "priority": {"type": {"key": {"type": "integer", + "minInteger": 0, + "maxInteger": 65535}}}, + "match": {"type": "string"}, + "actions": {"type": "string"}, + "external_ids": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}}, + "isRoot": true} + } +} diff --git a/ovn-br.xml b/ovn-br.xml new file mode 100644 index 0000000000..be6232094b --- /dev/null +++ b/ovn-br.xml @@ -0,0 +1,452 @@ +<?xml version="1.0" encoding="utf-8"?> +<database name="ovn-bridge-controller" title="OVN Bridge Controller Database"> + <p> + This database is the interface between OVN Bridge Controller and the + cloud management system (CMS) to program and control the OVS bridges + using OVN logical flows. The CMS produces almost all of + the contents of the database. The <code>ovn-bridge-controller</code> program + monitors the database contents and programs the OVS bridges with the + OpenFlow rules. + </p> + + <h2>External IDs</h2> + + <p> + Each of the tables in this database contains a special column, named + <code>external_ids</code>. This column has the same form and purpose each + place it appears. + </p> + + <dl> + <dt><code>external_ids</code>: map of string-string pairs</dt> + <dd> + Key-value pairs for use by the CMS. The CMS might use certain pairs, for + example, to identify entities in its own configuration that correspond to + those in this database. + </dd> + </dl> + + <table name="BR_Global" title="Bridge Controller configuration"> + <column name="br_cfg"> + Sequence number for client to increment. When a client modifies any + part of the bridge Controller database configuration and wishes to wait + for <code>ovn-br-controller</code> to finish applying the changes, it may + increment this sequence number. + </column> + + <group title="Common Columns"> + <column name="external_ids"> + See <em>External IDs</em> at the beginning of this document. + </column> + </group> + + <group title="Common options"> + <column name="options"> + This column provides general key/value settings. The supported + options are described individually below. + </column> + </group> + + <group title="Connection Options"> + <column name="connections"> + Database clients to which the Open vSwitch database server should + connect or on which it should listen, along with options for how these + connections should be configured. See the <ref table="Connection"/> + table for more information. + </column> + <column name="ssl"> + Global SSL/TLS configuration. + </column> + </group> + </table> + + <table name="Connection" title="OVSDB client connections."> + <p> + Configuration for a database connection to an Open vSwitch database + (OVSDB) client. + </p> + + <p> + This table primarily configures the Open vSwitch database server + (<code>ovsdb-server</code>). + </p> + + <p> + The Open vSwitch database server can initiate and maintain active + connections to remote clients. It can also listen for database + connections. + </p> + + <group title="Core Features"> + <column name="target"> + <p>Connection methods for clients.</p> + <p> + The following connection methods are currently supported: + </p> + <dl> + <dt><code>ssl:<var>host</var></code>[<code>:<var>port</var></code>]</dt> + <dd> + <p> + The specified SSL/TLS <var>port</var> on the host at the given + <var>host</var>, which can either be a DNS name (if built with + unbound library) or an IP address. A valid SSL/TLS configuration + must be provided when this form is used, this configuration can + be specified via command-line options or the <ref table="SSL"/> + table. + </p> + <p> + If <var>port</var> is not specified, it defaults to 6640. + </p> + <p> + SSL/TLS support is an optional feature that is not always + built as part of OVN or Open vSwitch. + </p> + </dd> + + <dt><code>tcp:<var>host</var></code>[<code>:<var>port</var></code>]</dt> + <dd> + <p> + The specified TCP <var>port</var> on the host at the given + <var>host</var>, which can either be a DNS name (if built with + unbound library) or an IP address. If <var>host</var> is an IPv6 + address, wrap it in square brackets, e.g. <code>tcp:[::1]:6640</code>. + </p> + <p> + If <var>port</var> is not specified, it defaults to 6640. + </p> + </dd> + <dt><code>pssl:</code>[<var>port</var>][<code>:<var>host</var></code>]</dt> + <dd> + <p> + Listens for SSL/TLS connections on the specified TCP + <var>port</var>. + Specify 0 for <var>port</var> to have the kernel automatically + choose an available port. If <var>host</var>, which can either + be a DNS name (if built with unbound library) or an IP address, + is specified, then connections are restricted to the resolved or + specified local IPaddress (either IPv4 or IPv6 address). If + <var>host</var> is an IPv6 address, wrap in square brackets, + e.g. <code>pssl:6640:[::1]</code>. If <var>host</var> is not + specified then it listens only on IPv4 (but not IPv6) addresses. + A valid SSL/TLS configuration must be provided when this form is + used, this can be specified either via command-line options or + the <ref table="SSL"/> table. + </p> + <p> + If <var>port</var> is not specified, it defaults to 6640. + </p> + <p> + SSL/TLS support is an optional feature that is not always built + as part of OVN or Open vSwitch. + </p> + </dd> + <dt><code>ptcp:</code>[<var>port</var>][<code>:<var>host</var></code>]</dt> + <dd> + <p> + Listens for connections on the specified TCP <var>port</var>. + Specify 0 for <var>port</var> to have the kernel automatically + choose an available port. If <var>host</var>, which can either + be a DNS name (if built with unbound library) or an IP address, + is specified, then connections are restricted to the resolved or + specified local IP address (either IPv4 or IPv6 address). If + <var>host</var> is an IPv6 address, wrap it in square brackets, + e.g. <code>ptcp:6640:[::1]</code>. If <var>host</var> is not + specified then it listens only on IPv4 addresses. + </p> + <p> + If <var>port</var> is not specified, it defaults to 6640. + </p> + </dd> + </dl> + <p>When multiple clients are configured, the <ref column="target"/> + values must be unique. Duplicate <ref column="target"/> values yield + unspecified results.</p> + </column> + </group> + + <group title="Client Failure Detection and Handling"> + <column name="max_backoff"> + Maximum number of milliseconds to wait between connection attempts. + Default is implementation-specific. + </column> + + <column name="inactivity_probe"> + Maximum number of milliseconds of idle time on connection to the client + before sending an inactivity probe message. If Open vSwitch does not + communicate with the client for the specified number of seconds, it + will send a probe. If a response is not received for the same + additional amount of time, Open vSwitch assumes the connection has been + broken and attempts to reconnect. Default is implementation-specific. + A value of 0 disables inactivity probes. + </column> + </group> + + <group title="Status"> + <p> + Key-value pair of <ref column="is_connected"/> is always updated. + Other key-value pairs in the status columns may be updated depends + on the <ref column="target"/> type. + </p> + + <p> + When <ref column="target"/> specifies a connection method that + listens for inbound connections (e.g. <code>ptcp:</code> or + <code>punix:</code>), both <ref column="n_connections"/> and + <ref column="is_connected"/> may also be updated while the + remaining key-value pairs are omitted. + </p> + + <p> + On the other hand, when <ref column="target"/> specifies an + outbound connection, all key-value pairs may be updated, except + the above-mentioned two key-value pairs associated with inbound + connection targets. They are omitted. + </p> + + <column name="is_connected"> + <code>true</code> if currently connected to this client, + <code>false</code> otherwise. + </column> + + <column name="status" key="last_error"> + A human-readable description of the last error on the connection + to the manager; i.e. <code>strerror(errno)</code>. This key + will exist only if an error has occurred. + </column> + + <column name="status" key="state" + type='{"type": "string", "enum": ["set", ["VOID", "BACKOFF", "CONNECTING", "ACTIVE", "IDLE"]]}'> + <p> + The state of the connection to the manager: + </p> + <dl> + <dt><code>VOID</code></dt> + <dd>Connection is disabled.</dd> + + <dt><code>BACKOFF</code></dt> + <dd>Attempting to reconnect at an increasing period.</dd> + + <dt><code>CONNECTING</code></dt> + <dd>Attempting to connect.</dd> + + <dt><code>ACTIVE</code></dt> + <dd>Connected, remote host responsive.</dd> + + <dt><code>IDLE</code></dt> + <dd>Connection is idle. Waiting for response to keep-alive.</dd> + </dl> + <p> + These values may change in the future. They are provided only for + human consumption. + </p> + </column> + + <column name="status" key="sec_since_connect" + type='{"type": "integer", "minInteger": 0}'> + The amount of time since this client last successfully connected + to the database (in seconds). Value is empty if client has never + successfully been connected. + </column> + + <column name="status" key="sec_since_disconnect" + type='{"type": "integer", "minInteger": 0}'> + The amount of time since this client last disconnected from the + database (in seconds). Value is empty if client has never + disconnected. + </column> + + <column name="status" key="locks_held"> + Space-separated list of the names of OVSDB locks that the connection + holds. Omitted if the connection does not hold any locks. + </column> + + <column name="status" key="locks_waiting"> + Space-separated list of the names of OVSDB locks that the connection is + currently waiting to acquire. Omitted if the connection is not waiting + for any locks. + </column> + + <column name="status" key="locks_lost"> + Space-separated list of the names of OVSDB locks that the connection + has had stolen by another OVSDB client. Omitted if no locks have been + stolen from this connection. + </column> + + <column name="status" key="n_connections" + type='{"type": "integer", "minInteger": 2}'> + When <ref column="target"/> specifies a connection method that + listens for inbound connections (e.g. <code>ptcp:</code> or + <code>pssl:</code>) and more than one connection is actually active, + the value is the number of active connections. Otherwise, this + key-value pair is omitted. + </column> + + <column name="status" key="bound_port" type='{"type": "integer"}'> + When <ref column="target"/> is <code>ptcp:</code> or + <code>pssl:</code>, this is the TCP port on which the OVSDB server is + listening. (This is particularly useful when <ref + column="target"/> specifies a port of 0, allowing the kernel to + choose any available port.) + </column> + </group> + + <group title="Common Columns"> + The overall purpose of these columns is described under <code>Common + Columns</code> at the beginning of this document. + + <column name="external_ids"/> + <column name="other_config"/> + </group> + </table> + + <table name="SSL"> + SSL/TLS configuration for ovn-nb database access. + + <column name="private_key"> + Name of a PEM file containing the private key used as the switch's + identity for SSL/TLS connections to the controller. + </column> + + <column name="certificate"> + Name of a PEM file containing a certificate, signed by the + certificate authority (CA) used by the controller and manager, + that certifies the switch's private key, identifying a trustworthy + switch. + </column> + + <column name="ca_cert"> + Name of a PEM file containing the CA certificate used to verify + that the switch is connected to a trustworthy controller. + </column> + + <column name="bootstrap_ca_cert"> + If set to <code>true</code>, then Open vSwitch will attempt to + obtain the CA certificate from the controller on its first SSL/TLS + 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/TLS connections must be authenticated by a certificate signed + by the CA certificate thus obtained. <em>This option exposes the + SSL/TLS connection to a man-in-the-middle attack obtaining the initial + CA certificate.</em> It may still be useful for bootstrapping. + </column> + + <column name="ssl_protocols"> + <p> + Range or a comma- or space-delimited list of the SSL/TLS protocols to + enable for SSL/TLS connections. + </p> + <p> + Supported protocols include <code>TLSv1.2</code> and + <code>TLSv1.3</code>. Ranges can be provided in a form of two protocol + names separated with a dash (<code>TLSv1.2-TLSv1.3</code>), or as a + single protocol name with a plus sign (<code>TLSv1.2+</code>). The + value can be a list of protocols or exactly one range. The range is a + preferred way of specifying protocols and the configuration always + behaves as if the range between the minimum and the maximum specified + version is provided, i.e., if the value is set to + <code>TLSv1.X,TLSv1.(X+2)</code>, the <code>TLSv1.(X+1)</code> will + also be enabled as if it was a range. + Regardless of order, the highest protocol supported by both sides will + be chosen when making the connection. + </p> + <p> + The default when this option is omitted is <code>TLSv1.2+</code>. + </p> + </column> + + <column name="ssl_ciphers"> + List of ciphers (in OpenSSL cipher string format) to be supported + for SSL/TLS connections with TLSv1.2. The default when this option + is omitted is <code>DEFAULT:@SECLEVEL=2</code>. + </column> + + <column name="ssl_ciphersuites"> + List of ciphersuites (in OpenSSL ciphersuites string format) to be + supported for SSL/TLS connections with TLSv1.3 and later. Default value + from OpenSSL will be used when this option is omitted. + </column> + + <group title="Common Columns"> + The overall purpose of these columns is described under <code>Common + Columns</code> at the beginning of this document. + + <column name="external_ids"/> + </group> + </table> + + <table name="Bridge" title="OVS Bridge to control"> + <column name="name"> + Name of the OVS bridge. This bridge should exist in the local OVS + database. + </column> + + <column name="options"> + Reserved for future use. + </column> + + <column name="external_ids"> + See <em>External IDs</em> at the beginning of this document. + </column> + </table> + + <table name="Logical_Flow" title="Logical flow"> + <column name="bridge"> + The bridge to which the logical flow belongs to. + </column> + + <column name="table_id"> + The stage in the logical pipeline, analogous to an OpenFlow table number. + </column> + + <column name="priority"> + The flow's priority. Flows with numerically higher priority take + precedence over those with lower. If two logical flows with the + same priority both match, then the one actually applied to the packet is + undefined. + </column> + + <column name="match"> + <p> + A matching expression. OVN provides a superset of OpenFlow matching + capabilities, using a syntax similar to Boolean expressions in a + programming language. + </p> + + <p> + Please see the documentation of the + <ref + column="match" table="Logical_Flow" db="OVN_Southbound"/> + column of the <ref table="Logical_Flow" + db="OVN_Southbound"/> table in the <ref db="OVN_Southbound"/> database. + </p> + </column> + + <column name="actions"> + <p> + Logical datapath actions, to be executed when the logical flow + represented by this row is the highest-priority match. + </p> + + <p> + Actions share lexical syntax with the <ref column="match"/> column. An + empty set of actions (or one that contains just white space or + comments), or a set of actions that consists of just + <code>drop;</code>, causes the matched packets to be dropped. + Otherwise, the column should contain a sequence of actions, each + terminated by a semicolon. + </p> + + <p> + Please see the documentation of the + <ref + column="actions" table="Logical_Flow" db="OVN_Southbound"/> + column of the <ref table="Logical_Flow" + db="OVN_Southbound"/> table in the <ref db="OVN_Southbound"/> database. + </p> + </column> + + <column name="external_ids"> + See <em>External IDs</em> at the beginning of this document. + </column> + </table> +</database> diff --git a/tutorial/ovn-sandbox b/tutorial/ovn-sandbox index ed334d1c31..239db58f35 100755 --- a/tutorial/ovn-sandbox +++ b/tutorial/ovn-sandbox @@ -64,6 +64,8 @@ gdb_ovn_controller=false gdb_ovn_controller_ex=false gdb_ovn_controller_vtep=false gdb_ovn_controller_vtep_ex=false +gdb_ovn_br_controller=false +gdb_ovn_br_controller_ex=false builddir= ovsbuilddir= srcdir= @@ -90,6 +92,7 @@ ic_nb_servers=3 ic_sb_model=clustered ic_sb_servers=3 dummy=override +ovn_br=false for option; do # This option-parsing mechanism borrowed from a Autoconf-generated @@ -339,6 +342,9 @@ EOF --ic-sb-m*) prev=ic_sb_model ;; + --ovn-br) + ovn_br=true + ;; -R|--gdb-run) gdb_vswitchd_ex=true gdb_ovsdb_ex=true @@ -422,6 +428,11 @@ if $built; then echo >&2 'source directory not found, please use --srcdir' exit 1 fi + br_schema=$srcdir/ovn-br.ovsschema + if test ! -e "$br_schema"; then + echo >&2 'source directory not found, please use --srcdir' + exit 1 + fi # Put built tools early in $PATH. if test ! -e $ovsbuilddir/vswitchd/ovs-vswitchd; then @@ -429,7 +440,7 @@ if $built; then exit 1 fi PATH=$ovsbuilddir/ovsdb:$ovsbuilddir/vswitchd:$ovsbuilddir/utilities:$ovsbuilddir/vtep:$PATH - PATH=$builddir/controller:$builddir/controller-vtep:$builddir/northd:$builddir/ic:$builddir/utilities:$PATH + PATH=$builddir/controller:$builddir/controller-vtep:$builddir/northd:$builddir/ic:$builddir/utilities:$builddir/br-controller:$PATH export PATH else case $schema in @@ -616,6 +627,10 @@ ovn_start_db sb "$sbdb_model" "$sbdb_servers" "$ovnsb_source" ovn_start_db ic_nb "$ic_nb_model" "$ic_nb_servers" "$ic_nb_schema" ovn_start_db ic_sb "$ic_sb_model" "$ic_sb_servers" "$ic_sb_schema" +if $ovn_br; then + ovn_start_db br standalone 1 "$br_schema" +fi + #Add a small delay to allow ovsdb-server to launch. sleep 0.1 @@ -692,6 +707,13 @@ rungdb $gdb_ovn_controller_vtep $gdb_ovn_controller_vtep_ex \ $OVN_CTRLR_PKI --log-file -vsyslog:off \ --ovnsb-db="$OVN_SB_DB" +if $ovn_br; then + run ovs-vsctl set open . external-ids:ovn-br-remote=$OVN_BR_DB + rungdb $gdb_ovn_br_controller $gdb_ovn_br_controller_ex ovn-br-controller \ + --detach --no-chdir -vsyslog:off \ + --log-file=ovn-br-controller.log \ + --pidfile=ovn-br-controller.pid -vconsole:off +fi cat <<EOF diff --git a/utilities/automake.mk b/utilities/automake.mk index 1de33614f9..3e3f2e08c9 100644 --- a/utilities/automake.mk +++ b/utilities/automake.mk @@ -119,4 +119,12 @@ bin_PROGRAMS += utilities/ovn-debug utilities_ovn_debug_SOURCES = utilities/ovn-debug.c utilities_ovn_debug_LDADD = lib/libovn.la $(OVSDB_LIBDIR)/libovsdb.la $(OVS_LIBDIR)/libopenvswitch.la +# ovn-brctl +bin_PROGRAMS += utilities/ovn-brctl +utilities_ovn_brctl_SOURCES = \ + utilities/ovn-dbctl.c \ + utilities/ovn-dbctl.h \ + utilities/ovn-brctl.c +utilities_ovn_brctl_LDADD = lib/libovn.la $(OVSDB_LIBDIR)/libovsdb.la $(OVS_LIBDIR)/libopenvswitch.la + include utilities/bugtool/automake.mk diff --git a/utilities/ovn-brctl.c b/utilities/ovn-brctl.c new file mode 100644 index 0000000000..051a897748 --- /dev/null +++ b/utilities/ovn-brctl.c @@ -0,0 +1,585 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #include <config.h> + +#include <getopt.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> + +/* OVS includes. */ +#include "command-line.h" +#include "daemon.h" +#include "db-ctl-base.h" +#include "dirs.h" +#include "openvswitch/vlog.h" + +/* OVN includes. */ +#include "lib/ovn-br-idl.h" +#include "lib/ovn-dirs.h" +#include "lib/ovn-util.h" +#include "lib/vec.h" +#include "ovn-dbctl.h" + +VLOG_DEFINE_THIS_MODULE(brctl); + +static char * OVS_WARN_UNUSED_RESULT br_by_name_or_uuid( + struct ctl_context *ctx, const char *id, bool must_exist, + const struct ovnbrrec_bridge **br_p); + +static void +brctl_add_base_prerequisites(struct ovsdb_idl *idl, + enum nbctl_wait_type wait_type OVS_UNUSED) +{ + ovsdb_idl_add_table(idl, &ovnbrrec_table_br_global); +} + +static void +brctl_pre_execute(struct ovsdb_idl *idl, struct ovsdb_idl_txn *txn, + enum nbctl_wait_type wait_type OVS_UNUSED) +{ + const struct ovnbrrec_br_global *br = ovnbrrec_br_global_first(idl); + if (!br) { + br = ovnbrrec_br_global_insert(txn); + } +} + +static int +get_inactivity_probe(struct ovsdb_idl *idl) +{ + const struct ovnbrrec_br_global *pr = ovnbrrec_br_global_first(idl); + int interval = DEFAULT_UTILS_PROBE_INTERVAL_MSEC; + + if (pr) { + interval = smap_get_int(&pr->options, "brctl_probe_interval", + interval); + } + + return interval; +} + +/* ovn-brctl specific context. Inherits the 'struct ctl_context' as base. */ +struct brctl_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; +}; + +/* Casts 'base' into 'struct sbctl_context'. */ +static struct brctl_context * +brctl_context_cast(struct ctl_context *base) +{ + return CONTAINER_OF(base, struct brctl_context, base); +} + +static struct ctl_context * +brctl_ctx_create(void) +{ + struct brctl_context *prctx = xmalloc(sizeof *prctx); + *prctx = (struct brctl_context) { + .cache_valid = false, + }; + return &prctx->base; +} + +static void +brctl_context_invalidate_cache(struct ctl_context *ctx) +{ + struct brctl_context *brctl_ctx = brctl_context_cast(ctx); + + if (!brctl_ctx->cache_valid) { + return; + } + brctl_ctx->cache_valid = false; +} + +static void +brctl_ctx_destroy(struct ctl_context *ctx) +{ + brctl_context_invalidate_cache(ctx); + free(ctx); +} + +static void +print_br(const struct ovnbrrec_bridge *br, struct ds *s) +{ + ds_put_format(s, "bridge "UUID_FMT" (%s)\n", + UUID_ARGS(&br->header_.uuid), br->name); +} + +static void +brctl_init(struct ctl_context *ctx OVS_UNUSED) +{ +} + +static void +brctl_pre_show(struct ctl_context *ctx) +{ + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_name); + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_options); + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_external_ids); +} + +static const struct ctl_table_class tables[OVNBRREC_N_TABLES] = { + [OVNBRREC_TABLE_BRIDGE].row_ids[0] + = {&ovnbrrec_bridge_col_name, NULL, NULL}, + + [OVNBRREC_TABLE_CONNECTION].row_ids[0] + = {&ovnbrrec_connection_col_target, NULL, NULL}, +}; + +static void +brctl_show(struct ctl_context *ctx) +{ + const struct ovnbrrec_bridge *br; + + if (ctx->argc == 2) { + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &br); + if (error) { + ctx->error = error; + return; + } + + print_br(br, &ctx->output); + } else { + OVNBRREC_BRIDGE_FOR_EACH (br, ctx->idl) { + print_br(br, &ctx->output); + } + } +} + +static void +pre_get_info(struct ctl_context *ctx) +{ + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_name); + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_options); + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_external_ids); + + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_actions); + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_bridge); + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_match); + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_priority); + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_table_id); + ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_external_ids); +} + +static bool +is_uuid_with_prefix(const char *uuid) +{ + return uuid[0] == '0' && (uuid[1] == 'x' || uuid[1] == 'X'); +} + +static const char * +strip_leading_zero(const char *s) +{ + return s + strspn(s, "0"); +} + +static bool +is_partial_uuid_match(const struct uuid *uuid, const char *match) +{ + char uuid_s[UUID_LEN + 1]; + snprintf(uuid_s, sizeof uuid_s, UUID_FMT, UUID_ARGS(uuid)); + + /* We strip leading zeros because we want to accept cookie values derived + * from UUIDs, and cookie values are printed without leading zeros because + * they're just numbers. */ + const char *s1 = strip_leading_zero(uuid_s); + const char *s2 = match; + if (is_uuid_with_prefix(s2)) { + s2 = s2 + 2; + } + s2 = strip_leading_zero(s2); + return !strncmp(s1, s2, strlen(s2)); +} + + +struct brctl_lflow { + const struct ovnbrrec_logical_flow *lflow; + const struct ovnbrrec_bridge *br; +}; + +static int +brctl_lflow_cmp(const void *a_, const void *b_) +{ + const struct brctl_lflow *a_ctl_lflow = a_; + const struct brctl_lflow *b_ctl_lflow = b_; + + const struct ovnbrrec_logical_flow *a = a_ctl_lflow->lflow; + const struct ovnbrrec_logical_flow *b = b_ctl_lflow->lflow; + + const struct ovnbrrec_bridge *abr = a_ctl_lflow->br; + const struct ovnbrrec_bridge *bbr = b_ctl_lflow->br; + const char *a_name = abr->name; + const char *b_name = bbr->name; + int cmp = strcmp(a_name, b_name); + if (cmp) { + return cmp; + } + + cmp = uuid_compare_3way(&abr->header_.uuid, &bbr->header_.uuid); + if (cmp) { + return cmp; + } + + cmp = (a->table_id > b->table_id ? 1 + : a->table_id < b->table_id ? -1 + : a->priority > b->priority ? -1 + : a->priority < b->priority ? 1 + : strcmp(a->match, b->match)); + return cmp ? cmp : strcmp(a->actions, b->actions); +} + +static void +brctl_lflow_add(struct vector *lflows, + const struct ovnbrrec_logical_flow *lflow, + const struct ovnbrrec_bridge *br) +{ + struct brctl_lflow brctl_lflow = (struct brctl_lflow) { + .lflow = lflow, + .br = br, + }; + vector_push(lflows, &brctl_lflow); +} + +static void +print_uuid_part(const struct uuid *uuid, bool do_print, struct ds *s) +{ + if (!do_print) { + return; + } + ds_put_format(s, "uuid=0x%08"PRIx32", ", uuid->parts[0]); +} + +static void +print_bridge_prompt(const struct ovnbrrec_bridge *br, + const struct uuid *uuid, struct ds *s) { + ds_put_format(s, "Bridge: %s", br->name); + ds_put_format(s, " ("UUID_FMT")\n", + UUID_ARGS(uuid)); +} + +static char * OVS_WARN_UNUSED_RESULT +parse_priority(const char *arg, int64_t *priority_p) +{ + /* Validate priority. */ + int64_t priority; + if (!ovs_scan(arg, "%"SCNd64, &priority) + || priority < 0 || priority > 32767) { + /* Priority_p could be uninitialized as no valid priority was + * input, initialize it to a valid value of 0 before returning */ + *priority_p = 0; + return xasprintf("%s: priority must in range 0...32767", arg); + } + *priority_p = priority; + return NULL; +} + +static char * OVS_WARN_UNUSED_RESULT +parse_table_id(const char *arg, int64_t *table_p) +{ + /* Validate table id. */ + int64_t table; + if (!ovs_scan(arg, "%"SCNd64, &table) + || table < 0 || table > 55) { + /* table_p could be uninitialized as no valid table id was + * input, initialize it to a valid value of 0 before returning */ + *table_p = 0; + return xasprintf("%s: table must in range 0...55", arg); + } + *table_p = table; + return NULL; +} + +static char * OVS_WARN_UNUSED_RESULT +br_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist, + const struct ovnbrrec_bridge **br_p) +{ + const struct ovsdb_idl_row *row = NULL; + + char *error = ctl_get_row(ctx, &ovnbrrec_table_bridge, + id, must_exist, &row); + *br_p = (const struct ovnbrrec_bridge *) row; + return error; +} + +static void +cmd_br_add(struct ctl_context *ctx) +{ + const struct ovnbrrec_bridge *bridge = NULL; + + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], false, &bridge); + if (error) { + ctl_error(ctx, "%s", error); + free(error); + return; + } + + if (bridge) { + ctl_error(ctx, "Bridge %s already exists", ctx->argv[1]); + } + + bridge = ovnbrrec_bridge_insert(ctx->txn); + ovnbrrec_bridge_set_name(bridge, ctx->argv[1]); +} + +static void +cmd_br_del(struct ctl_context *ctx) +{ + const struct ovnbrrec_bridge *bridge = NULL; + + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &bridge); + if (error) { + ctl_error(ctx, "%s", error); + free(error); + return; + } + + ovnbrrec_bridge_delete(bridge); +} + +static void +cmd_lflow_list(struct ctl_context *ctx) +{ + struct vector lflows = VECTOR_EMPTY_INITIALIZER(struct brctl_lflow); + const struct ovnbrrec_bridge *bridge = NULL; + + if (ctx->argc > 1) { + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], false, &bridge); + if (error) { + ctl_error(ctx, "%s", error); + free(error); + return; + } + + if (bridge) { + ctx->argc--; + ctx->argv++; + } + } + + const struct ovnbrrec_logical_flow *lflow; + OVNBRREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->idl) { + if (bridge && lflow->bridge != bridge) { + continue; + } + + brctl_lflow_add(&lflows, lflow, lflow->bridge); + } + + vector_qsort(&lflows, brctl_lflow_cmp); + + bool print_uuid = shash_find(&ctx->options, "--uuid") != NULL; + + const struct brctl_lflow *curr, *prev = NULL; + VECTOR_FOR_EACH_PTR (&lflows, curr) { + /* Figure out whether to print this particular flow. By default, we + * print all flows, but if any UUIDs were listed on the command line + * then we only print the matching ones. */ + bool include; + if (ctx->argc > 1) { + include = false; + for (size_t j = 1; j < ctx->argc; j++) { + if (is_partial_uuid_match(&curr->lflow->header_.uuid, + ctx->argv[j])) { + include = true; + break; + } + } + } else { + include = true; + } + if (!include) { + continue; + } + + /* Print a header line for this datapath or pipeline, if we haven't + * already done so. */ + if (!prev || prev->br != curr->br) { + print_bridge_prompt(curr->br, &curr->br->header_.uuid, + &ctx->output); + } + + /* Print the flow. */ + ds_put_cstr(&ctx->output, " "); + print_uuid_part(&curr->lflow->header_.uuid, print_uuid, &ctx->output); + ds_put_format(&ctx->output, + "table=%-2"PRId64", priority=%-5"PRId64 + ", match=(%s), action=(%s)\n", + curr->lflow->table_id, + curr->lflow->priority, curr->lflow->match, + curr->lflow->actions); + prev = curr; + } + + vector_destroy(&lflows); +} + +static void +cmd_lflow_add(struct ctl_context *ctx) +{ + const struct ovnbrrec_bridge *bridge = NULL; + + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &bridge); + if (error) { + ctl_error(ctx, "%s", error); + free(error); + return; + } + + int64_t table_id; + error = parse_table_id(ctx->argv[2], &table_id); + if (error) { + ctx->error = error; + return; + } + + int64_t priority; + error = parse_priority(ctx->argv[3], &priority); + if (error) { + ctx->error = error; + return; + } + + struct ovnbrrec_logical_flow *lflow = + ovnbrrec_logical_flow_insert(ctx->txn); + ovnbrrec_logical_flow_set_bridge(lflow, bridge); + ovnbrrec_logical_flow_set_table_id(lflow, table_id); + ovnbrrec_logical_flow_set_priority(lflow, priority); + ovnbrrec_logical_flow_set_match(lflow, ctx->argv[4]); + ovnbrrec_logical_flow_set_actions(lflow, ctx->argv[5]); +} + +static void +cmd_lflow_del(struct ctl_context *ctx) +{ + const struct ovnbrrec_logical_flow *lflow; + const struct ovsdb_idl_row *row; + + char *error = ctl_get_row(ctx, &ovnbrrec_table_logical_flow, + ctx->argv[1], true, &row); + if (error) { + ctl_error(ctx, "%s", error); + free(error); + return; + } + + lflow = (const struct ovnbrrec_logical_flow *) row; + ovnbrrec_logical_flow_delete(lflow); +} + +static void +cmd_lflows_del(struct ctl_context *ctx) +{ + const struct ovnbrrec_bridge *bridge = NULL; + + if (ctx->argc > 1) { + char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &bridge); + if (error) { + ctl_error(ctx, "%s", error); + free(error); + return; + } + } + + const struct ovnbrrec_logical_flow *lflow; + OVNBRREC_LOGICAL_FLOW_FOR_EACH_SAFE (lflow, ctx->idl) { + if (!bridge || lflow->bridge == bridge) { + ovnbrrec_logical_flow_delete(lflow); + } + } +} + +static const struct ctl_command_syntax brctl_commands[] = { + { "init", 0, 0, "", NULL, brctl_init, NULL, "", RW }, + { "show", 0, 1, "[BRIDGE]", brctl_pre_show, brctl_show, NULL, "", RO }, + + /* Bridge commands. */ + {"add-br", 1, 1, "BRIDGE", pre_get_info, cmd_br_add, NULL, + "", RW}, + {"del-br", 1, 1, "BRIDGE", pre_get_info, cmd_br_del, NULL, + "", RW}, + + /* Logical flow commands */ + {"lflow-list", 0, INT_MAX, "[BRIDGE] [LFLOW...]", + pre_get_info, cmd_lflow_list, NULL, + "--uuid,--ovs?,--stats,--vflows?", RO}, + {"dump-flows", 0, INT_MAX, "[DATAPATH] [LFLOW...]", + pre_get_info, cmd_lflow_list, NULL, + "--uuid,--ovs?,--stats,--vflows?", + RO}, /* Friendly alias for lflow-list */ + {"add-flow", 5, 5, "BRIDGE TABLE PRIORITY MATCH ACTION", + pre_get_info, cmd_lflow_add, NULL, + "", RW}, + {"del-flow", 1, 1, "UUID", pre_get_info, cmd_lflow_del, NULL, + "", RW}, + {"del-flows", 0, 1, "[BRIDGE]", pre_get_info, cmd_lflows_del, NULL, + "", RW}, + {NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, RO}, +}; + +static void +brctl_usage(void) +{ + printf("\ +%s: OVN Provider DB management utility\n\ +usage: %s [OPTIONS] COMMAND [ARG...]\n\ +\n\ +General commands:\n\ + init initialize the database\n\ + show print overview of database contents\n\ +\n\ +Logical flow commands:\n\ + lflow-list [BRIDGE] [LFLOW...] list logical flows for BRIDGE\n\ + dump-flows [BRIDGE] [LFLOW...] alias for lflow-list\n\ +\n\ +\n\n", program_name, program_name); +} + +int +main(int argc, char *argv[]) +{ + struct ovn_dbctl_options dbctl_options = { + .db_version = ovnbrrec_get_db_version(), + .default_db = default_br_db(), + .allow_wait = false, + + .options_env_var_name = "OVN_brctl_OPTIONS", + .daemon_env_var_name = "OVN_PR_DAEMON", + + .idl_class = &ovnbrrec_idl_class, + .tables = tables, + .cmd_show_table = NULL, + .commands = brctl_commands, + + .usage = brctl_usage, + .add_base_prerequisites = brctl_add_base_prerequisites, + .pre_execute = brctl_pre_execute, + .post_execute = NULL, + .get_inactivity_probe = get_inactivity_probe, + + .ctx_create = brctl_ctx_create, + .ctx_destroy = brctl_ctx_destroy, + }; + + return ovn_dbctl_main(argc, argv, &dbctl_options); +}