From patchwork Tue Apr 25 11:05:28 2017
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
X-Patchwork-Submitter: Daniel Alvarez Sanchez
X-Patchwork-Id: 754748
Return-Path:
X-Original-To: incoming@patchwork.ozlabs.org
Delivered-To: patchwork-incoming@bilbo.ozlabs.org
Received: from mail.linuxfoundation.org (mail.linuxfoundation.org
[140.211.169.12])
(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256
bits)) (No client certificate requested)
by ozlabs.org (Postfix) with ESMTPS id 3wC0jc1lydz9s80
for ;
Tue, 25 Apr 2017 21:05:08 +1000 (AEST)
Received: from mail.linux-foundation.org (localhost [127.0.0.1])
by mail.linuxfoundation.org (Postfix) with ESMTP id 16F88B44;
Tue, 25 Apr 2017 11:05:03 +0000 (UTC)
X-Original-To: dev@openvswitch.org
Delivered-To: ovs-dev@mail.linuxfoundation.org
Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org
[172.17.192.35])
by mail.linuxfoundation.org (Postfix) with ESMTPS id E7B39B30
for ; Tue, 25 Apr 2017 11:05:01 +0000 (UTC)
X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6
Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28])
by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 35F0779
for ; Tue, 25 Apr 2017 11:05:00 +0000 (UTC)
Received: from smtp.corp.redhat.com
(int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15])
(using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits))
(No client certificate requested)
by mx1.redhat.com (Postfix) with ESMTPS id 7D2C76408A
for ; Tue, 25 Apr 2017 11:04:59 +0000 (UTC)
DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com 7D2C76408A
Authentication-Results: ext-mx09.extmail.prod.ext.phx2.redhat.com;
dmarc=none (p=none dis=none) header.from=redhat.com
Authentication-Results: ext-mx09.extmail.prod.ext.phx2.redhat.com;
spf=pass smtp.mailfrom=dalvarez@redhat.com
DKIM-Filter: OpenDKIM Filter v2.11.0 mx1.redhat.com 7D2C76408A
Received: from devstack.novalocal (unknown [10.16.19.47])
by smtp.corp.redhat.com (Postfix) with ESMTP id 12B397FE93;
Tue, 25 Apr 2017 11:04:58 +0000 (UTC)
From: Daniel Alvarez
To: dev@openvswitch.org
Date: Tue, 25 Apr 2017 11:05:28 +0000
Message-Id: <1493118328-21311-1-git-send-email-dalvarez@redhat.com>
X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15
X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16
(mx1.redhat.com [10.5.110.38]);
Tue, 25 Apr 2017 11:04:59 +0000 (UTC)
X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI,
RP_MATCHES_RCVD autolearn=ham version=3.3.1
X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on
smtp1.linux-foundation.org
Subject: [ovs-dev] [PATCH] OVN localport type support
X-BeenThere: ovs-dev@openvswitch.org
X-Mailman-Version: 2.1.12
Precedence: list
List-Id:
List-Unsubscribe: ,
List-Archive:
List-Post:
List-Help:
List-Subscribe: ,
MIME-Version: 1.0
Sender: ovs-dev-bounces@openvswitch.org
Errors-To: ovs-dev-bounces@openvswitch.org
This patch introduces a new type of OVN ports called "localport".
These ports will be present in every hypervisor and may have the
same IP/MAC addresses. They are not bound to any chassis and traffic
to these ports will never go through a tunnel.
Its main use case is the OpenStack metadata API support which relies
on a local agent running on every hypervisor and serving metadata to
VM's locally. This service is described in detail at [0].
Signed-off-by: Daniel Alvarez
---
ovn/controller/binding.c | 87 ++++++++++++++++++++----
ovn/controller/ovn-controller.8.xml | 15 +++++
ovn/controller/physical.c | 85 ++++++++++++++++++------
ovn/northd/ovn-northd.8.xml | 8 +--
ovn/northd/ovn-northd.c | 6 +-
ovn/ovn-architecture.7.xml | 20 ++++--
ovn/ovn-nb.xml | 9 +++
ovn/ovn-sb.xml | 16 ++++-
tests/ovn.at | 129 ++++++++++++++++++++++++++++++++++++
9 files changed, 329 insertions(+), 46 deletions(-)
diff --git a/ovn/controller/binding.c b/ovn/controller/binding.c
index 95e9deb..a0fb778 100644
--- a/ovn/controller/binding.c
+++ b/ovn/controller/binding.c
@@ -352,6 +352,43 @@ setup_qos(const char *egress_iface, struct hmap *queue_map)
hmap_destroy(&consistent_queues);
netdev_close(netdev_phy);
}
+static bool
+set_ovsport_external_ids(struct controller_ctx *ctx,
+ const struct ovsrec_bridge *bridge,
+ const struct ovsrec_interface *iface_rec,
+ const char *key,
+ const char *value)
+{
+ if (!ctx->ovs_idl_txn || !bridge || !iface_rec || !key) {
+ return false;
+ }
+
+ /* Find the port with this interface and add the key/value pair to its
+ * external_ids. Assume that an interface corresponds only to one port. */
+ int i;
+ for (i = 0; i < bridge->n_ports; i++) {
+ const struct ovsrec_port *port_rec = bridge->ports[i];
+ int j;
+
+ if (!strcmp(port_rec->name, bridge->name)) {
+ continue;
+ }
+
+ for (j = 0; j < port_rec->n_interfaces; j++) {
+ if (port_rec->interfaces[j] == iface_rec) {
+ struct smap new_ids;
+ smap_clone(&new_ids, &port_rec->external_ids);
+ smap_replace(&new_ids, key, value);
+ ovsrec_port_verify_external_ids(port_rec);
+ ovsrec_port_set_external_ids(port_rec, &new_ids);
+ smap_destroy(&new_ids);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
static void
consider_local_datapath(struct controller_ctx *ctx,
@@ -359,6 +396,7 @@ consider_local_datapath(struct controller_ctx *ctx,
const struct lport_index *lports,
const struct sbrec_chassis *chassis_rec,
const struct sbrec_port_binding *binding_rec,
+ const struct ovsrec_bridge *bridge,
struct hmap *qos_map,
struct hmap *local_datapaths,
struct shash *lport_to_iface,
@@ -368,19 +406,40 @@ consider_local_datapath(struct controller_ctx *ctx,
= shash_find_data(lport_to_iface, binding_rec->logical_port);
bool our_chassis = false;
- if (iface_rec
- || (binding_rec->parent_port && binding_rec->parent_port[0] &&
- sset_contains(local_lports, binding_rec->parent_port))) {
- if (binding_rec->parent_port && binding_rec->parent_port[0]) {
- /* Add child logical port to the set of all local ports. */
- sset_add(local_lports, binding_rec->logical_port);
- }
- add_local_datapath(ldatapaths, lports, binding_rec->datapath,
- false, local_datapaths);
- if (iface_rec && qos_map && ctx->ovs_idl_txn) {
- get_qos_params(binding_rec, qos_map);
+
+ if (iface_rec && !strcmp(binding_rec->type, "localport")) {
+ /* Make sure localport external_id is present, otherwise set it
+ * for both interface and port. This should only happen the first
+ * time. We set the value on both the interface and port because it's
+ * most convenient to check for the value here on the interface. Later,
+ * we need to read this value in physical.c, and expect to read it from
+ * the port there.*/
+ if (!smap_get(&iface_rec->external_ids, "ovn-localport-port")) {
+ if (set_ovsport_external_ids(ctx, bridge, iface_rec,
+ "ovn-localport-port",
+ binding_rec->logical_port)) {
+ struct smap new_ids;
+ smap_clone(&new_ids, &iface_rec->external_ids);
+ smap_replace(&new_ids, "ovn-localport-port",
+ binding_rec->logical_port);
+ ovsrec_interface_verify_external_ids(iface_rec);
+ ovsrec_interface_set_external_ids(iface_rec, &new_ids);
+ smap_destroy(&new_ids);
+ }
}
- our_chassis = true;
+ } else if (iface_rec
+ || (binding_rec->parent_port && binding_rec->parent_port[0] &&
+ sset_contains(local_lports, binding_rec->parent_port))) {
+ if (binding_rec->parent_port && binding_rec->parent_port[0]) {
+ /* Add child logical port to the set of all local ports. */
+ sset_add(local_lports, binding_rec->logical_port);
+ }
+ add_local_datapath(ldatapaths, lports, binding_rec->datapath,
+ false, local_datapaths);
+ if (iface_rec && qos_map && ctx->ovs_idl_txn) {
+ get_qos_params(binding_rec, qos_map);
+ }
+ our_chassis = true;
} else if (!strcmp(binding_rec->type, "l2gateway")) {
const char *chassis_id = smap_get(&binding_rec->options,
"l2gateway-chassis");
@@ -468,8 +527,8 @@ binding_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int,
consider_local_datapath(ctx, ldatapaths, lports,
chassis_rec, binding_rec,
sset_is_empty(&egress_ifaces) ? NULL :
- &qos_map, local_datapaths, &lport_to_iface,
- local_lports);
+ br_int, &qos_map, local_datapaths,
+ &lport_to_iface, local_lports);
}
diff --git a/ovn/controller/ovn-controller.8.xml b/ovn/controller/ovn-controller.8.xml
index f9cbbfe..e6d5cd5 100644
--- a/ovn/controller/ovn-controller.8.xml
+++ b/ovn/controller/ovn-controller.8.xml
@@ -305,6 +305,21 @@
logical patch port that it implements.
+
+
+ external_ids:ovn-localport-port
in the Port
+ and Interface
tables
+
+
+
+
+ The presence of this key identifies a port as a
+ localport
so that ovn-controller
can
+ properly set the right flows to allow only local traffic and
+ drop any packets directed to an external chassis.
+
+
+
Runtime Management Commands
diff --git a/ovn/controller/physical.c b/ovn/controller/physical.c
index 0f1aa63..6a745f8 100644
--- a/ovn/controller/physical.c
+++ b/ovn/controller/physical.c
@@ -59,6 +59,8 @@ physical_register_ovs_idl(struct ovsdb_idl *ovs_idl)
static struct simap localvif_to_ofport =
SIMAP_INITIALIZER(&localvif_to_ofport);
static struct hmap tunnels = HMAP_INITIALIZER(&tunnels);
+static struct simap localport_to_ofport =
+ SIMAP_INITIALIZER(&localport_to_ofport);
/* Maps from a chassis to the OpenFlow port number of the tunnel that can be
* used to reach that chassis. */
@@ -601,6 +603,27 @@ consider_port_binding(enum mf_field_id mff_ovn_geneve,
} else {
/* Remote port connected by tunnel */
+ /* Table 32, priority 150.
+ * =======================
+ *
+ * Drop traffic originated from a localport to a remote destination.
+ */
+ struct simap_node *localport;
+ SIMAP_FOR_EACH (localport, &localport_to_ofport) {
+ int inport;
+ if ((inport = simap_get(&localport_to_ofport, localport->name))) {
+ match_init_catchall(&match);
+ ofpbuf_clear(ofpacts_p);
+ /* Match localport in_port. */
+ match_set_in_port(&match, inport);
+ /* Match MFF_LOG_DATAPATH, MFF_LOG_OUTPORT. */
+ match_set_metadata(&match, htonll(dp_key));
+ match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
+ ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0,
+ &match, ofpacts_p);
+ }
+ }
+
/* Table 32, priority 100.
* =======================
*
@@ -753,6 +776,34 @@ consider_mc_group(enum mf_field_id mff_ovn_geneve,
sset_destroy(&remote_chassis);
}
+static bool
+simap_sync(struct simap *old, const struct simap *new)
+{
+ struct simap_node *vif_name, *vif_name_next;
+ bool has_changed = false;
+
+ SIMAP_FOR_EACH_SAFE (vif_name, vif_name_next, old) {
+ int newport;
+ if ((newport = simap_get(new, vif_name->name))) {
+ if (newport != simap_get(old, vif_name->name)) {
+ simap_put(old, vif_name->name, newport);
+ has_changed = true;
+ }
+ } else {
+ simap_find_and_delete(old, vif_name->name);
+ has_changed = true;
+ }
+ }
+ SIMAP_FOR_EACH (vif_name, new) {
+ if (!simap_get(old, vif_name->name)) {
+ simap_put(old, vif_name->name,
+ simap_get(new, vif_name->name));
+ has_changed = true;
+ }
+ }
+ return has_changed;
+}
+
void
physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
const struct ovsrec_bridge *br_int,
@@ -768,6 +819,9 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
SIMAP_INITIALIZER(&new_localvif_to_ofport);
struct simap new_tunnel_to_ofport =
SIMAP_INITIALIZER(&new_tunnel_to_ofport);
+ struct simap new_localport_to_ofport =
+ SIMAP_INITIALIZER(&new_localport_to_ofport);
+
for (int i = 0; i < br_int->n_ports; i++) {
const struct ovsrec_port *port_rec = br_int->ports[i];
if (!strcmp(port_rec->name, br_int->name)) {
@@ -784,6 +838,8 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
"ovn-localnet-port");
const char *l2gateway = smap_get(&port_rec->external_ids,
"ovn-l2gateway-port");
+ const char *localport = smap_get(&port_rec->external_ids,
+ "ovn-localport-port");
for (int j = 0; j < port_rec->n_interfaces; j++) {
const struct ovsrec_interface *iface_rec = port_rec->interfaces[j];
@@ -848,6 +904,9 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
"iface-id");
if (iface_id) {
simap_put(&new_localvif_to_ofport, iface_id, ofport);
+ if (localport) {
+ simap_put(&new_localport_to_ofport, localport, ofport);
+ }
}
}
}
@@ -864,26 +923,11 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
}
/* Capture changed or removed openflow ports. */
- struct simap_node *vif_name, *vif_name_next;
- SIMAP_FOR_EACH_SAFE (vif_name, vif_name_next, &localvif_to_ofport) {
- int newport;
- if ((newport = simap_get(&new_localvif_to_ofport, vif_name->name))) {
- if (newport != simap_get(&localvif_to_ofport, vif_name->name)) {
- simap_put(&localvif_to_ofport, vif_name->name, newport);
- physical_map_changed = true;
- }
- } else {
- simap_find_and_delete(&localvif_to_ofport, vif_name->name);
- physical_map_changed = true;
- }
- }
- SIMAP_FOR_EACH (vif_name, &new_localvif_to_ofport) {
- if (!simap_get(&localvif_to_ofport, vif_name->name)) {
- simap_put(&localvif_to_ofport, vif_name->name,
- simap_get(&new_localvif_to_ofport, vif_name->name));
- physical_map_changed = true;
- }
- }
+ physical_map_changed = simap_sync(&localvif_to_ofport,
+ &new_localvif_to_ofport);
+ /* Do the same for all localports. */
+ physical_map_changed |= simap_sync(&localport_to_ofport,
+ &new_localport_to_ofport);
if (physical_map_changed) {
/* Reprocess logical flow table immediately. */
poll_immediate_wake();
@@ -1043,4 +1087,5 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
simap_destroy(&new_localvif_to_ofport);
simap_destroy(&new_tunnel_to_ofport);
+ simap_destroy(&new_localport_to_ofport);
}
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
index ab8fd88..c4552c6 100644
--- a/ovn/northd/ovn-northd.8.xml
+++ b/ovn/northd/ovn-northd.8.xml
@@ -521,8 +521,8 @@ output;
- These flows are omitted for logical ports (other than router ports)
- that are down.
+ These flows are omitted for logical ports (other than router ports or
+ localport
ports) that are down.
@@ -548,8 +548,8 @@ nd_na {
- These flows are omitted for logical ports (other than router ports)
- that are down.
+ These flows are omitted for logical ports (other than router ports or
+ localport
ports) that are down.
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index d0a5ba2..126c89f 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -3252,9 +3252,11 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
/*
* Add ARP/ND reply flows if either the
* - port is up or
- * - port type is router
+ * - port type is router or
+ * - port type is localport
*/
- if (!lsp_is_up(op->nbsp) && strcmp(op->nbsp->type, "router")) {
+ if (!lsp_is_up(op->nbsp) && strcmp(op->nbsp->type, "router") &&
+ strcmp(op->nbsp->type, "localport")) {
continue;
}
diff --git a/ovn/ovn-architecture.7.xml b/ovn/ovn-architecture.7.xml
index d8114f1..2866666 100644
--- a/ovn/ovn-architecture.7.xml
+++ b/ovn/ovn-architecture.7.xml
@@ -409,6 +409,15 @@
logical patch ports at each such point of connectivity, one on
each side.
+
+ Localport ports represent the points of local
+ connectivity between logical switches and VIFs. These ports are
+ present in every chassis (not bound to any particular one) and
+ traffic from them will never go through a tunnel. A
+ localport
is expected to only generate traffic destined
+ for a local destination, typically in response to a request it
+ received.
+
@@ -986,11 +995,12 @@
hypervisor. Each flow's actions implement sending a packet to the port
it matches. For unicast logical output ports on remote hypervisors,
the actions set the tunnel key to the correct value, then send the
- packet on the tunnel port to the correct hypervisor. (When the remote
- hypervisor receives the packet, table 0 there will recognize it as a
- tunneled packet and pass it along to table 33.) For multicast logical
- output ports, the actions send one copy of the packet to each remote
- hypervisor, in the same way as for unicast destinations. If a
+ packet on the tunnel port to the correct hypervisor (unless the packet
+ comes from a localport, in which case it will be dropped). (When the
+ remote hypervisor receives the packet, table 0 there will recognize it
+ as a tunneled packet and pass it along to table 33.) For multicast
+ logical output ports, the actions send one copy of the packet to each
+ remote hypervisor, in the same way as for unicast destinations. If a
multicast group includes a logical port or ports on the local
hypervisor, then its actions also resubmit to table 33. Table 32 also
includes a fallback flow that resubmits to table 33 if there is no
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 7a1c20e..9b73692 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -259,6 +259,15 @@
to model direct connectivity to an existing network.
+ localport
+
+ A connection to a local VIF. Traffic that arrives on a
+ localport
is never forwarded over a tunnel to another
+ chassis. These ports are present on every chassis and have the same
+ address in all of them. This is used to model connectivity to local
+ services that run on every hypervisor.
+
+
l2gateway
A connection to a physical network.
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index 5542f7e..b4cfd71 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -311,7 +311,7 @@
transmitted and received with reasonable performance. It is a hint
to senders transmitting data to this chassis that they should use
checksums to protect OVN metadata. ovn-controller
- populates this key with the value defined in
+ populates this key with the value defined in
column
of the Open_vSwitch database's table. Other applications should treat this key as
@@ -1739,6 +1739,11 @@ tcp.flags = RST;
connectivity to the corresponding physical network.
+ localport
+
+ Always empty. A localport port is present on every chassis.
+
+
l3gateway
The physical location of the L3 gateway. To successfully identify a
@@ -1819,6 +1824,15 @@ tcp.flags = RST;
to model direct connectivity to an existing network.
+ localport
+
+ A connection to a local VIF. Traffic that arrives on a
+ localport
is never forwarded over a tunnel to another
+ chassis. These ports are present on every chassis and have the same
+ address in all of them. This is used to model connectivity to local
+ services that run on every hypervisor.
+
+
l2gateway
An L2 connection to a physical network. The chassis this
diff --git a/tests/ovn.at b/tests/ovn.at
index 088bbf6..22ba1ac 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -6994,3 +6994,132 @@ OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
OVN_CLEANUP([hv1],[hv2])
AT_CLEANUP
+
+AT_SETUP([ovn -- 2 HVs, 1 lport/HV, localport ports])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+ovn-nbctl ls-add ls1
+
+# Add localport to the switch
+ovn-nbctl lsp-add ls1 lp01
+ovn-nbctl lsp-set-addresses lp01 f0:00:00:00:00:01
+ovn-nbctl lsp-set-type lp01 localport
+
+net_add n1
+
+for i in 1 2; do
+ sim_add hv$i
+ as hv$i
+ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.$i
+ ovs-vsctl add-port br-int vif01 -- \
+ set Interface vif01 external-ids:iface-id=lp01 \
+ options:tx_pcap=hv${i}/vif01-tx.pcap \
+ options:rxq_pcap=hv${i}/vif01-rx.pcap \
+ ofport-request=${i}0
+
+ ovs-vsctl add-port br-int vif${i}1 -- \
+ set Interface vif${i}1 external-ids:iface-id=lp${i}1 \
+ options:tx_pcap=hv${i}/vif${i}1-tx.pcap \
+ options:rxq_pcap=hv${i}/vif${i}1-rx.pcap \
+ ofport-request=${i}1
+
+ ovn-nbctl lsp-add ls1 lp${i}1
+ ovn-nbctl lsp-set-addresses lp${i}1 f0:00:00:00:00:${i}1
+ ovn-nbctl lsp-set-port-security lp${i}1 f0:00:00:00:00:${i}1
+
+ ovn-sbctl list port_binding
+ ovn-sbctl show
+ ovn-sbctl list chassis
+ ovs-vsctl list interface
+ ovs-vsctl list open
+ ovn-nbctl lsp-get-up lp${i}1
+ ps -ef |grep ovn-controller
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp${i}1` = xup])
+done
+
+ovn-nbctl --wait=sb sync
+ovn-sbctl dump-flows
+
+ovn_populate_arp
+
+# Given the name of a logical port, prints the name of the hypervisor
+# on which it is located.
+vif_to_hv() {
+ echo hv${1%?}
+}
+#
+# test_packet INPORT DST SRC ETHTYPE EOUT LOUT DEFHV
+#
+# 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). INPORT is specified as
+# logical switch port numbers, e.g. 11 for vif11.
+#
+# EOUT is the end-to-end output port, that is, where the packet will end up
+# after possibly bouncing through one or more localnet ports. LOUT is the
+# logical output port, which might be a localnet port, as seen by ovn-trace
+# (which doesn't know what localnet ports are connected to and therefore can't
+# figure out the end-to-end answer).
+#
+# DEFHV is the default hypervisor from where the packet is going to be sent
+# if the source port is a localport.
+for i in 1 2; do
+ for j in 0 1; do
+ : > $i$j.expected
+ done
+done
+test_packet() {
+ local inport=$1 dst=$2 src=$3 eth=$4 eout=$5 lout=$6 defhv=$7
+ echo "$@"
+
+ # First try tracing the packet.
+ uflow="inport==\"lp$inport\" && eth.dst==$dst && eth.src==$src && eth.type==0x$eth"
+ if test $lout != drop; then
+ echo "output(\"$lout\");"
+ fi > expout
+ AT_CAPTURE_FILE([trace])
+ AT_CHECK([ovn-trace --all ls1 "$uflow" | tee trace | sed '1,/Minimal trace/d'], [0], [expout])
+
+ # Then actually send a packet, for an end-to-end test.
+ local packet=$(echo $dst$src | sed 's/://g')${eth}
+ hv=`vif_to_hv $inport`
+ # If hypervisor 0 (localport) use the defhv parameter
+ if test $hv == hv0; then
+ hv=$defhv
+ fi
+ vif=vif$inport
+ as $hv ovs-appctl netdev-dummy/receive $vif $packet
+ if test $eout != drop; then
+ echo $packet >> ${eout#lp}.expected
+ fi
+}
+
+
+# lp11 and lp21 are on different hypervisors
+test_packet 11 f0:00:00:00:00:21 f0:00:00:00:00:11 1121 lp21 lp21
+test_packet 21 f0:00:00:00:00:11 f0:00:00:00:00:21 2111 lp11 lp11
+
+# Both VIFs should be able to reach the localport on their own HV
+test_packet 11 f0:00:00:00:00:01 f0:00:00:00:00:11 1101 lp01 lp01
+test_packet 21 f0:00:00:00:00:01 f0:00:00:00:00:21 2101 lp01 lp01
+
+# Packet sent from localport on same hv should reach the vif
+test_packet 01 f0:00:00:00:00:11 f0:00:00:00:00:01 0111 lp11 lp11 hv1
+test_packet 01 f0:00:00:00:00:21 f0:00:00:00:00:01 0121 lp21 lp21 hv2
+
+# Packet sent from localport on different hv should be dropped
+test_packet 01 f0:00:00:00:00:21 f0:00:00:00:00:01 0121 drop lp21 hv1
+test_packet 01 f0:00:00:00:00:11 f0:00:00:00:00:01 0111 drop lp11 hv2
+
+# Now check the packets actually received against the ones expected.
+for i in 1 2; do
+ for j in 0 1; do
+ OVN_CHECK_PACKETS([hv$i/vif$i$j-tx.pcap], [$i$j.expected])
+ done
+done
+
+OVN_CLEANUP([hv1],[hv2])
+
+AT_CLEANUP