@@ -154,6 +154,15 @@ binding_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int,
}
sbrec_port_binding_set_chassis(binding_rec, chassis_rec);
}
+ } else if (!strcmp(binding_rec->type, "l2gateway")
+ && binding_rec->chassis == chassis_rec) {
+ /* A locally bound gateway port.
+ *
+ * ovn-controller does not bind gateway ports itself.
+ * Choosing a chassis for a gateway port is left
+ * up to an entity external to OVN. */
+ sset_add(all_lports, binding_rec->logical_port);
+ add_local_datapath(local_datapaths, binding_rec);
} else if (chassis_rec && binding_rec->chassis == chassis_rec
&& strcmp(binding_rec->type, "gateway")) {
if (ctx->ovnsb_idl_txn) {
@@ -184,10 +184,10 @@
The presence of this key identifies a patch port as one created by
<code>ovn-controller</code> to connect the integration bridge and
another bridge to implement a <code>localnet</code> logical port.
- Its value is the name of the logical port with type=localnet that
- the port implements.
- See <code>external_ids:ovn-bridge-mappings</code>, above,
- for more information.
+ Its value is the name of the logical port with <code>type</code>
+ set to <code>localnet</code> that the port implements. See
+ <code>external_ids:ovn-bridge-mappings</code>, above, for more
+ information.
</p>
<p>
@@ -199,6 +199,29 @@
</dd>
<dt>
+ <code>external-ids:ovn-gateway-port</code> in the <code>Port</code>
+ table
+ </dt>
+ <dd>
+ <p>
+ The presence of this key identifies a patch port as one created by
+ <code>ovn-controller</code> to connect the integration bridge and
+ another bridge to implement a <code>gateway</code> logical port.
+ Its value is the name of the logical port with <code>type</code>
+ set to <code>gateway</code> that the port implements. See
+ <code>external_ids:ovn-bridge-mappings</code>, above, for more
+ information.
+ </p>
+
+ <p>
+ Each <code>gateway</code> logical port is implemented as a pair
+ of patch ports, one in the integration bridge, one in a different
+ bridge, with the same <code>external-ids:ovn-gateway-port</code>
+ value.
+ </p>
+ </dd>
+
+ <dt>
<code>external-ids:ovn-logical-patch-port</code> in the
<code>Port</code> table
</dt>
@@ -134,7 +134,8 @@ static void
add_bridge_mappings(struct controller_ctx *ctx,
const struct ovsrec_bridge *br_int,
struct shash *existing_ports,
- struct hmap *local_datapaths)
+ struct hmap *local_datapaths,
+ const char *chassis_id)
{
/* Get ovn-bridge-mappings. */
const char *mappings_cfg = "";
@@ -175,6 +176,7 @@ add_bridge_mappings(struct controller_ctx *ctx,
const struct sbrec_port_binding *binding;
SBREC_PORT_BINDING_FOR_EACH (binding, ctx->ovnsb_idl) {
+ const char *patch_port_id;
if (!strcmp(binding->type, "localnet")) {
struct local_datapath *ld
= get_local_datapath(local_datapaths,
@@ -195,31 +197,40 @@ add_bridge_mappings(struct controller_ctx *ctx,
continue;
}
ld->localnet_port = binding;
+ patch_port_id = "ovn-localnet-port";
+ } else if (!strcmp(binding->type, "l2gateway")) {
+ if (!binding->chassis || strcmp(chassis_id, binding->chassis->name)) {
+ /* This gateway port is not bound to this chassis, so we should
+ * not create any patch ports for it. */
+ continue;
+ }
+ patch_port_id = "ovn-gateway-port";
} else {
- /* Not a binding for a localnet port. */
+ /* not a localnet or gateway port. */
continue;
}
const char *network = smap_get(&binding->options, "network_name");
if (!network) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
- VLOG_ERR_RL(&rl, "localnet port '%s' has no network name.",
- binding->logical_port);
+ VLOG_ERR_RL(&rl, "%s port '%s' has no network name.",
+ binding->type, binding->logical_port);
continue;
}
struct ovsrec_bridge *br_ln = shash_find_data(&bridge_mappings, network);
if (!br_ln) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
- VLOG_ERR_RL(&rl, "bridge not found for localnet port '%s' "
- "with network name '%s'", binding->logical_port, network);
+ VLOG_ERR_RL(&rl, "bridge not found for %s port '%s' "
+ "with network name '%s'",
+ binding->type, binding->logical_port, network);
continue;
}
char *name1 = patch_port_name(br_int->name, binding->logical_port);
char *name2 = patch_port_name(binding->logical_port, br_int->name);
- create_patch_port(ctx, "ovn-localnet-port", binding->logical_port,
+ create_patch_port(ctx, patch_port_id, binding->logical_port,
br_int, name1, br_ln, name2, existing_ports);
- create_patch_port(ctx, "ovn-localnet-port", binding->logical_port,
+ create_patch_port(ctx, patch_port_id, binding->logical_port,
br_ln, name2, br_int, name1, existing_ports);
free(name1);
free(name2);
@@ -327,8 +338,9 @@ patch_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int,
struct shash existing_ports = SHASH_INITIALIZER(&existing_ports);
const struct ovsrec_port *port;
OVSREC_PORT_FOR_EACH (port, ctx->ovs_idl) {
- if (smap_get(&port->external_ids, "ovn-localnet-port") ||
- smap_get(&port->external_ids, "ovn-logical-patch-port")) {
+ if (smap_get(&port->external_ids, "ovn-localnet-port")
+ || smap_get(&port->external_ids, "ovn-gateway-port")
+ || smap_get(&port->external_ids, "ovn-logical-patch-port")) {
shash_add(&existing_ports, port->name, port);
}
}
@@ -336,7 +348,7 @@ patch_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int,
/* Create in the database any patch ports that should exist. Remove from
* 'existing_ports' any patch ports that do exist in the database and
* should be there. */
- add_bridge_mappings(ctx, br_int, &existing_ports, local_datapaths);
+ add_bridge_mappings(ctx, br_int, &existing_ports, local_datapaths, chassis_id);
add_logical_patch_ports(ctx, br_int, chassis_id, &existing_ports,
patched_datapaths);
@@ -169,6 +169,8 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
const char *localnet = smap_get(&port_rec->external_ids,
"ovn-localnet-port");
+ const char *gateway = smap_get(&port_rec->external_ids,
+ "ovn-gateway-port");
const char *logpatch = smap_get(&port_rec->external_ids,
"ovn-logical-patch-port");
@@ -191,6 +193,10 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
/* localnet patch ports can be handled just like VIFs. */
simap_put(&localvif_to_ofport, localnet, ofport);
break;
+ } else if (is_patch && gateway) {
+ /* gateway patch ports can be handled just like VIFs. */
+ simap_put(&localvif_to_ofport, gateway, ofport);
+ break;
} else if (is_patch && logpatch) {
/* Logical patch ports can be handled just like VIFs. */
simap_put(&localvif_to_ofport, logpatch, ofport);
@@ -269,13 +275,13 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
* OpenFlow port for the VIF. 'tun' will be NULL.
*
* The same logic handles logical patch ports, as well as
- * localnet patch ports.
+ * localnet and gateway patch ports.
*
* For a container nested inside a VM and accessible via a VLAN,
* 'tag' is the VLAN ID; otherwise 'tag' is 0.
*
- * For a localnet patch port, if a VLAN ID was configured, 'tag'
- * is set to that VLAN ID; otherwise 'tag' is 0.
+ * For a localnet or gateway patch port, if a VLAN ID was
+ * configured, 'tag' is set to that VLAN ID; otherwise 'tag' is 0.
*
* - If the port is on a remote chassis, the OpenFlow port for a
* tunnel to the VIF's remote chassis. 'tun' identifies that
@@ -297,7 +303,9 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
} else {
ofport = u16_to_ofp(simap_get(&localvif_to_ofport,
binding->logical_port));
- if (!strcmp(binding->type, "localnet") && ofport && binding->tag) {
+ if ((!strcmp(binding->type, "localnet")
+ || !strcmp(binding->type, "l2gateway"))
+ && ofport && binding->tag) {
tag = *binding->tag;
}
}
@@ -355,7 +363,8 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
/* Match a VLAN tag and strip it, including stripping priority tags
* (e.g. VLAN ID 0). In the latter case we'll add a second flow
* for frames that lack any 802.1Q header later. */
- if (tag || !strcmp(binding->type, "localnet")) {
+ if (tag || !strcmp(binding->type, "localnet")
+ || !strcmp(binding->type, "l2gateway")) {
match_set_dl_vlan(&match, htons(tag));
ofpact_put_STRIP_VLAN(&ofpacts);
}
@@ -392,7 +401,8 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG,
tag ? 150 : 100, &match, &ofpacts);
- if (!tag && !strcmp(binding->type, "localnet")) {
+ if (!tag && (!strcmp(binding->type, "localnet")
+ || !strcmp(binding->type, "l2gateway"))) {
/* Add a second flow for frames that lack any 802.1Q
* header. For these, drop the OFPACT_STRIP_VLAN
* action. */
@@ -134,6 +134,11 @@
to model direct connectivity to an existing network.
</dd>
+ <dt><code>l2gateway</code></dt>
+ <dd>
+ A connection to a physical network.
+ </dd>
+
<dt><code>vtep</code></dt>
<dd>
A port to a logical switch on a VTEP gateway.
@@ -175,6 +180,20 @@
</column>
</group>
+ <group title="Options for l2gateway ports">
+ <p>
+ These options apply when <ref column="type"/> is
+ <code>l2gateway</code>.
+ </p>
+
+ <column name="options" key="network_name">
+ Required. The name of the network to which the <code>l2gateway</code>
+ port is connected. The gateway, via <code>ovn-controller</code>,
+ uses its local configuration to determine exactly how to connect to
+ this network.
+ </column>
+ </group>
+
<group title="Options for vtep ports">
<p>
These options apply when <ref column="type"/> is <code>vtep</code>.
@@ -658,7 +677,7 @@
</p>
</column>
</group>
-
+
<group title="Common Columns">
<column name="external_ids">
See <em>External IDs</em> at the beginning of this document.
@@ -1295,10 +1295,39 @@ tcp.flags = RST;
</column>
<column name="chassis">
- The physical location of the logical port. To successfully identify a
- chassis, this column must be a <ref table="Chassis"/> record. This is
- populated by
- <code>ovn-controller</code>/<code>ovn-controller-vtep</code>.
+ The meaning of this column depends on the value of the <ref column="type"/>
+ column. This is the meaning for each <ref column="type"/>
+
+ <dl>
+ <dt>(empty string)</dt>
+ <dd>
+ The physical location of the logical port. To successfully identify a
+ chassis, this column must be a <ref table="Chassis"/> record. This is
+ populated by <code>ovn-controller</code>.
+ </dd>
+
+ <dt>vtep</dt>
+ <dd>
+ The physical location of the hardware_vtep gateway. To successfully
+ identify a chassis, this column must be a <ref table="Chassis"/> record.
+ This is populated by <code>ovn-controller-vtep</code>.
+ </dd>
+
+ <dt>localnet</dt>
+ <dd>
+ Always empty. A localnet port is realized on every chassis that has
+ connectivity to the corresponding physical network.
+ </dd>
+
+ <dt>l2gateway</dt>
+ <dd>
+ The physical location of this L2 gateway. To successfully identify a
+ chassis, this column must be a <ref table="Chassis"/> record.
+ This is populated by an entity external to OVN, either manually or by
+ a CMS.
+ </dd>
+ </dl>
+
</column>
<column name="tunnel_key">
@@ -1362,6 +1391,14 @@ tcp.flags = RST;
to model direct connectivity to an existing network.
</dd>
+ <dt><code>l2gateway</code></dt>
+ <dd>
+ A connection to a physical network. The chassis this
+ <ref table="Port_Binding"/> is bound to will serve as
+ an L2 gateway to the network named by
+ <ref column="options" table="Port_Binding"/>:<code>network_name</code>.
+ </dd>
+
<dt><code>vtep</code></dt>
<dd>
A port to a logical switch on a VTEP gateway chassis. In order to
@@ -1444,6 +1481,36 @@ tcp.flags = RST;
</column>
</group>
+ <group title="Gateway Options">
+ <p>
+ These options apply to logical ports with <ref column="type"/> of
+ <code>l2gateway</code>.
+ </p>
+
+ <column name="options" key="network_name">
+ Required. <code>ovn-controller</code> uses the configuration entry
+ <code>ovn-bridge-mappings</code> to determine how to connect to this
+ network. <code>ovn-bridge-mappings</code> is a list of network names
+ mapped to a local OVS bridge that provides access to that network. An
+ example of configuring <code>ovn-bridge-mappings</code> would be:
+
+ <pre>$ ovs-vsctl set open . external-ids:ovn-bridge-mappings=physnet1:br-eth0,physnet2:br-eth1</pre>
+
+ <p>
+ When a logical switch has a <code>l2gateway</code> port attached,
+ the chassis that the <code>l2gateway</code> port is bound to
+ must have a bridge mapping configured to reach the network
+ identified by <code>network_name</code>.
+ </p>
+ </column>
+
+ <column name="tag">
+ If set, indicates that the gateway is connected to a specific
+ VLAN on the physical network. The VLAN ID is used to match
+ incoming traffic and is also added to outgoing traffic.
+ </column>
+ </group>
+
<group title="VTEP Options">
<p>
These options apply to logical ports with <ref column="type"/> of
@@ -1501,7 +1568,8 @@ tcp.flags = RST;
<p>
This column is used for a different purpose when <ref column="type"/>
- is <code>localnet</code> (see <code>Localnet Options</code>, above).
+ is <code>localnet</code> (see <code>Localnet Options</code>, above)
+ or <code>l2gateway</code> (see <code>Gateway Options</code>, above).
</p>
</column>
</group>
@@ -1250,6 +1250,170 @@ for sim in hv1 hv2 hv3 vtep main; do
done
AT_CLEANUP
+# Similar test to "hardware GW"
+AT_SETUP([ovn -- 3 HVs, 1 VIFs/HV, 1 software GW, 1 LS])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+# Configure the Northbound database
+ovn-nbctl ls-add lsw0
+
+ovn-nbctl lsp-add lsw0 lp1
+ovn-nbctl lsp-set-addresses lp1 f0:00:00:00:00:01
+
+ovn-nbctl lsp-add lsw0 lp2
+ovn-nbctl lsp-set-addresses lp2 f0:00:00:00:00:02
+
+ovn-nbctl lsp-add lsw0 lp-gw
+ovn-nbctl lsp-set-type lp-gw l2gateway
+ovn-nbctl lsp-set-options lp-gw network_name=physnet1
+ovn-nbctl lsp-set-addresses lp-gw unknown
+
+net_add n1 # Network to connect hv1, hv2, and gw
+net_add n2 # Network to connect gw and hv3
+
+# Create hypervisor hv1 connected to n1
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1
+
+# Create hypervisor hv2 connected to n1
+sim_add hv2
+as hv2
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap ofport-request=1
+
+# Create hypervisor hv_gw connected to n1 and n2
+# connect br-phys bridge to n1; connect hv-gw bridge to n2
+sim_add hv_gw
+as hv_gw
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.3
+ovs-vsctl add-br br-phys2
+net_attach n2 br-phys2
+ovs-vsctl set open . external_ids:ovn-bridge-mappings="physnet1:br-phys2"
+
+# Bind our gateway port to the hv_gw chassis
+ovn-sbctl lport-bind lp-gw hv_gw
+
+# Add hv3 on the other side of the GW
+sim_add hv3
+as hv3
+ovs-vsctl add-br br-phys
+net_attach n2 br-phys
+ovs-vsctl add-port br-phys vif3 -- set Interface vif3 options:tx_pcap=hv3/vif3-tx.pcap options:rxq_pcap=hv3/vif3-rx.pcap ofport-request=1
+
+
+# Pre-populate the hypervisors' ARP tables so that we don't lose any
+# packets for ARP resolution (native tunneling doesn't queue packets
+# for ARP resolution).
+ovn_populate_arp
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+# XXX This should be more systematic.
+sleep 1
+
+# test_packet INPORT DST SRC ETHTYPE OUTPORT...
+#
+# This shell function causes a packet to be received on INPORT. The packet's
+# content has Ethernet destination DST and source SRC (each exactly 12 hex
+# digits) and Ethernet type ETHTYPE (4 hex digits). The OUTPORTs (zero or
+# more) list the VIFs on which the packet should be received. INPORT and the
+# OUTPORTs are specified as lport numbers, e.g. 1 for vif1.
+trim_zeros() {
+ sed 's/\(00\)\{1,\}$//'
+}
+for i in 1 2 3; do
+ : > $i.expected
+done
+test_packet() {
+ local inport=$1 packet=$2$3$4; shift; shift; shift; shift
+ #hv=hv`echo $inport | sed 's/^\(.\).*/\1/'`
+ hv=hv$inport
+ vif=vif$inport
+ as $hv ovs-appctl netdev-dummy/receive $vif $packet
+ for outport; do
+ echo $packet | trim_zeros >> $outport.expected
+ done
+}
+
+# Send packets between all pairs of source and destination ports:
+#
+# 1. Unicast packets are delivered to exactly one lport (except that packets
+# destined to their input ports are dropped).
+#
+# 2. Broadcast and multicast are delivered to all lports except the input port.
+#
+# 3. The lswitch delivers packets with an unknown destination to lports with
+# "unknown" among their MAC addresses (and port security disabled).
+for s in 1 2 3 ; do
+ bcast=
+ unknown=
+ for d in 1 2 3 ; do
+ if test $d != $s; then unicast=$d; else unicast=; fi
+ test_packet $s f0000000000$d f0000000000$s 00$s$d $unicast #1
+
+ # The vtep (vif3) is the only one configured for "unknown"
+ if test $d != $s && test $d = 3; then
+ unknown="$unknown $d"
+ fi
+ bcast="$bcast $unicast"
+ done
+
+ test_packet $s ffffffffffff f0000000000$s 0${s}ff $bcast #2
+ test_packet $s 010000000000 f0000000000$s 0${s}ff $bcast #3
+ test_packet $s f0000000ffff f0000000000$s 0${s}66 $unknown #4
+done
+
+# Allow some time for packet forwarding.
+# XXX This can be improved.
+sleep 3
+
+echo "------ ovn-nbctl show ------"
+ovn-nbctl show
+echo "------ ovn-sbctl show ------"
+ovn-sbctl show
+
+echo "------ hv1 ------"
+as hv1 ovs-vsctl show
+echo "------ hv1 br-int ------"
+as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int
+echo "------ hv1 br-phys ------"
+as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-phys
+
+echo "------ hv2 ------"
+as hv2 ovs-vsctl show
+echo "------ hv2 br-int ------"
+as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int
+echo "------ hv2 br-phys ------"
+as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-phys
+
+echo "------ hv_gw ------"
+as hv_gw ovs-vsctl show
+echo "------ hv_gw br-phys ------"
+as hv_gw ovs-ofctl -O OpenFlow13 dump-flows br-phys
+echo "------ hv_gw br-phys2 ------"
+as hv_gw ovs-ofctl -O OpenFlow13 dump-flows br-phys2
+
+echo "------ hv3 ------"
+as hv3 ovs-vsctl show
+echo "------ hv3 br-phys ------"
+as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-phys
+
+# Now check the packets actually received against the ones expected.
+for i in 1 2 3; do
+ file=hv$i/vif$i-tx.pcap
+ echo $file
+ $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $file | trim_zeros > $i.packets
+ sort $i.expected > expout
+ AT_CHECK([sort $i.packets], [0], [expout])
+ echo
+done
+AT_CLEANUP
+
# 3 hypervisors, 3 logical switches with 3 logical ports each, 1 logical router
AT_SETUP([ovn -- 3 HVs, 3 LS, 3 lports/LS, 1 LR])
AT_SKIP_IF([test $HAVE_PYTHON = no])