ebb439
From 4f81eab659b42aeb8b433783515b03b5772c68de Mon Sep 17 00:00:00 2001
ebb439
From: Numan Siddique <numans@ovn.org>
ebb439
Date: Thu, 12 Nov 2020 17:26:17 +0530
ebb439
Subject: [PATCH 06/10] controller: Add load balancer hairpin OF flows.
ebb439
ebb439
Presently to handle the load balancer hairpin traffic (the traffic destined to the
ebb439
load balancer VIP is dnatted to the backend which originated the traffic), ovn-northd
ebb439
adds a lot of logical flows to check this scenario. This patch attempts to reduce the
ebb439
these logical flows. Each ovn-controller will read the load balancers from
ebb439
the newly added southbound Load_Balancer table and adds the load balancer hairpin OF
ebb439
flows in the table 68, 69 and 70. If suppose a below load balancer is configured
ebb439
ebb439
10.0.0.10:80 = 10.0.0.4:8080, 10.0.0.5:8090, then the below flows are added
ebb439
ebb439
table=68, ip.src = 10.0.0.4,ip.dst=10.0.0.4,tcp.dst=8080 actions=load:1->NXM_NX_REG10[7]
ebb439
table=68, ip.src = 10.0.0.5,ip.dst=10.0.0.5,tcp.dst=8090 actions=load:1->NXM_NX_REG10[7]
ebb439
table=69, ip.src = 10.0.0.4,ip.dst=10.0.0.10,tcp.src=8080 actions=load:1->NXM_NX_REG10[7]
ebb439
table=69, ip.src = 10.0.0.5,ip.dst=10.0.0.10,tcp.src=8090 actions=load:1->NXM_NX_REG10[7]
ebb439
table=70, ct.trk && ct.dnat && ct.nw_dst == 10.0.0.10. actions=ct(commit, zone=reg12, nat(src=10.0.0.5))
ebb439
ebb439
Upcoming patch will add OVN actions which does the lookup in these tables to handle the
ebb439
hairpin traffic.
ebb439
ebb439
Acked-by: Dumitru Ceara <dceara@redhat.com>
ebb439
Acked-by: Mark Michelson <mmichels@redhat.com>
ebb439
Signed-off-by: Numan Siddique <numans@ovn.org>
ebb439
ebb439
(cherry-picked from master commit 7da8145d9d4743b3d15f0d7f51b201e7ba87fe63)
ebb439
Conflicts:
ebb439
	tests/ovn.at
ebb439
---
ebb439
 controller/lflow.c           | 230 +++++++++++++++++
ebb439
 controller/lflow.h           |   6 +-
ebb439
 controller/ovn-controller.c  |  27 +-
ebb439
 include/ovn/logical-fields.h |   3 +
ebb439
 tests/ovn.at                 | 469 +++++++++++++++++++++++++++++++++++
ebb439
 5 files changed, 733 insertions(+), 2 deletions(-)
ebb439
ebb439
diff --git a/controller/lflow.c b/controller/lflow.c
ebb439
index 4d71dfddb..633fdfb7f 100644
ebb439
--- a/controller/lflow.c
ebb439
+++ b/controller/lflow.c
ebb439
@@ -26,6 +26,7 @@
ebb439
 #include "ovn-controller.h"
ebb439
 #include "ovn/actions.h"
ebb439
 #include "ovn/expr.h"
ebb439
+#include "lib/lb.h"
ebb439
 #include "lib/ovn-l7.h"
ebb439
 #include "lib/ovn-sb-idl.h"
ebb439
 #include "lib/extend-table.h"
ebb439
@@ -1144,6 +1145,190 @@ add_neighbor_flows(struct ovsdb_idl_index *sbrec_port_binding_by_name,
ebb439
     }
ebb439
 }
ebb439
 
ebb439
+static void
ebb439
+add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb,
ebb439
+                         struct ovn_lb_vip *lb_vip,
ebb439
+                         struct ovn_lb_backend *lb_backend,
ebb439
+                         uint8_t lb_proto,
ebb439
+                         struct ovn_desired_flow_table *flow_table)
ebb439
+{
ebb439
+    uint64_t stub[1024 / 8];
ebb439
+    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
ebb439
+
ebb439
+    uint8_t value = 1;
ebb439
+    put_load(&value, sizeof value, MFF_LOG_FLAGS,
ebb439
+             MLF_LOOKUP_LB_HAIRPIN_BIT, 1, &ofpacts);
ebb439
+
ebb439
+    struct match hairpin_match = MATCH_CATCHALL_INITIALIZER;
ebb439
+    struct match hairpin_reply_match = MATCH_CATCHALL_INITIALIZER;
ebb439
+
ebb439
+    if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
ebb439
+        ovs_be32 ip4 = in6_addr_get_mapped_ipv4(&lb_backend->ip);
ebb439
+
ebb439
+        match_set_dl_type(&hairpin_match, htons(ETH_TYPE_IP));
ebb439
+        match_set_nw_src(&hairpin_match, ip4);
ebb439
+        match_set_nw_dst(&hairpin_match, ip4);
ebb439
+
ebb439
+        match_set_dl_type(&hairpin_reply_match,
ebb439
+                          htons(ETH_TYPE_IP));
ebb439
+        match_set_nw_src(&hairpin_reply_match, ip4);
ebb439
+        match_set_nw_dst(&hairpin_reply_match,
ebb439
+                         in6_addr_get_mapped_ipv4(&lb_vip->vip));
ebb439
+    } else {
ebb439
+        match_set_dl_type(&hairpin_match, htons(ETH_TYPE_IPV6));
ebb439
+        match_set_ipv6_src(&hairpin_match, &lb_backend->ip);
ebb439
+        match_set_ipv6_dst(&hairpin_match, &lb_backend->ip);
ebb439
+
ebb439
+        match_set_dl_type(&hairpin_reply_match,
ebb439
+                          htons(ETH_TYPE_IPV6));
ebb439
+        match_set_ipv6_src(&hairpin_reply_match, &lb_backend->ip);
ebb439
+        match_set_ipv6_dst(&hairpin_reply_match, &lb_vip->vip);
ebb439
+    }
ebb439
+
ebb439
+    if (lb_backend->port) {
ebb439
+        match_set_nw_proto(&hairpin_match, lb_proto);
ebb439
+        match_set_tp_dst(&hairpin_match, htons(lb_backend->port));
ebb439
+
ebb439
+        match_set_nw_proto(&hairpin_reply_match, lb_proto);
ebb439
+        match_set_tp_src(&hairpin_reply_match, htons(lb_backend->port));
ebb439
+    }
ebb439
+
ebb439
+    for (size_t i = 0; i < lb->slb->n_datapaths; i++) {
ebb439
+        match_set_metadata(&hairpin_match,
ebb439
+                           htonll(lb->slb->datapaths[i]->tunnel_key));
ebb439
+        match_set_metadata(&hairpin_reply_match,
ebb439
+                           htonll(lb->slb->datapaths[i]->tunnel_key));
ebb439
+
ebb439
+        ofctrl_add_flow(flow_table, OFTABLE_CHK_LB_HAIRPIN, 100,
ebb439
+                        lb->slb->header_.uuid.parts[0], &hairpin_match,
ebb439
+                        &ofpacts, &lb->slb->header_.uuid);
ebb439
+
ebb439
+        ofctrl_add_flow(flow_table, OFTABLE_CHK_LB_HAIRPIN_REPLY, 100,
ebb439
+                        lb->slb->header_.uuid.parts[0],
ebb439
+                        &hairpin_reply_match,
ebb439
+                        &ofpacts, &lb->slb->header_.uuid);
ebb439
+    }
ebb439
+
ebb439
+    ofpbuf_uninit(&ofpacts);
ebb439
+}
ebb439
+
ebb439
+static void
ebb439
+add_lb_ct_snat_vip_flows(struct ovn_controller_lb *lb,
ebb439
+                         struct ovn_lb_vip *lb_vip,
ebb439
+                         struct ovn_desired_flow_table *flow_table)
ebb439
+{
ebb439
+    uint64_t stub[1024 / 8];
ebb439
+    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
ebb439
+
ebb439
+    struct ofpact_conntrack *ct = ofpact_put_CT(&ofpacts);
ebb439
+    ct->recirc_table = NX_CT_RECIRC_NONE;
ebb439
+    ct->zone_src.field = mf_from_id(MFF_LOG_SNAT_ZONE);
ebb439
+    ct->zone_src.ofs = 0;
ebb439
+    ct->zone_src.n_bits = 16;
ebb439
+    ct->flags = NX_CT_F_COMMIT;
ebb439
+    ct->alg = 0;
ebb439
+
ebb439
+    size_t nat_offset;
ebb439
+    nat_offset = ofpacts.size;
ebb439
+    ofpbuf_pull(&ofpacts, nat_offset);
ebb439
+
ebb439
+    struct ofpact_nat *nat = ofpact_put_NAT(&ofpacts);
ebb439
+    nat->flags = NX_NAT_F_SRC;
ebb439
+    nat->range_af = AF_UNSPEC;
ebb439
+
ebb439
+    if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
ebb439
+        nat->range_af = AF_INET;
ebb439
+        nat->range.addr.ipv4.min = in6_addr_get_mapped_ipv4(&lb_vip->vip);
ebb439
+    } else {
ebb439
+        nat->range_af = AF_INET6;
ebb439
+        nat->range.addr.ipv6.min = lb_vip->vip;
ebb439
+    }
ebb439
+    ofpacts.header = ofpbuf_push_uninit(&ofpacts, nat_offset);
ebb439
+    ofpact_finish(&ofpacts, &ct->ofpact);
ebb439
+
ebb439
+    struct match match = MATCH_CATCHALL_INITIALIZER;
ebb439
+    if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
ebb439
+        match_set_dl_type(&match, htons(ETH_TYPE_IP));
ebb439
+        match_set_ct_nw_dst(&match, nat->range.addr.ipv4.min);
ebb439
+    } else {
ebb439
+        match_set_dl_type(&match, htons(ETH_TYPE_IPV6));
ebb439
+        match_set_ct_ipv6_dst(&match, &lb_vip->vip);
ebb439
+    }
ebb439
+
ebb439
+    uint32_t ct_state = OVS_CS_F_TRACKED | OVS_CS_F_DST_NAT;
ebb439
+    match_set_ct_state_masked(&match, ct_state, ct_state);
ebb439
+
ebb439
+    for (size_t i = 0; i < lb->slb->n_datapaths; i++) {
ebb439
+        match_set_metadata(&match,
ebb439
+                           htonll(lb->slb->datapaths[i]->tunnel_key));
ebb439
+
ebb439
+        ofctrl_add_flow(flow_table, OFTABLE_CT_SNAT_FOR_VIP, 100,
ebb439
+                        lb->slb->header_.uuid.parts[0],
ebb439
+                        &match, &ofpacts, &lb->slb->header_.uuid);
ebb439
+    }
ebb439
+
ebb439
+    ofpbuf_uninit(&ofpacts);
ebb439
+}
ebb439
+
ebb439
+static void
ebb439
+consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb,
ebb439
+                          const struct hmap *local_datapaths,
ebb439
+                          struct ovn_desired_flow_table *flow_table)
ebb439
+{
ebb439
+    /* Check if we need to add flows or not.  If there is one datapath
ebb439
+     * in the local_datapaths, it means all the datapaths of the lb
ebb439
+     * will be in the local_datapaths. */
ebb439
+    size_t i;
ebb439
+    for (i = 0; i < sbrec_lb->n_datapaths; i++) {
ebb439
+        if (get_local_datapath(local_datapaths,
ebb439
+                               sbrec_lb->datapaths[i]->tunnel_key)) {
ebb439
+            break;
ebb439
+        }
ebb439
+    }
ebb439
+
ebb439
+    if (i == sbrec_lb->n_datapaths) {
ebb439
+        return;
ebb439
+    }
ebb439
+
ebb439
+    struct ovn_controller_lb *lb = ovn_controller_lb_create(sbrec_lb);
ebb439
+    uint8_t lb_proto = IPPROTO_TCP;
ebb439
+    if (lb->slb->protocol && lb->slb->protocol[0]) {
ebb439
+        if (!strcmp(lb->slb->protocol, "udp")) {
ebb439
+            lb_proto = IPPROTO_UDP;
ebb439
+        } else if (!strcmp(lb->slb->protocol, "sctp")) {
ebb439
+            lb_proto = IPPROTO_SCTP;
ebb439
+        }
ebb439
+    }
ebb439
+
ebb439
+    for (i = 0; i < lb->n_vips; i++) {
ebb439
+        struct ovn_lb_vip *lb_vip = &lb->vips[i];
ebb439
+
ebb439
+        for (size_t j = 0; j < lb_vip->n_backends; j++) {
ebb439
+            struct ovn_lb_backend *lb_backend = &lb_vip->backends[j];
ebb439
+
ebb439
+            add_lb_vip_hairpin_flows(lb, lb_vip, lb_backend, lb_proto,
ebb439
+                                     flow_table);
ebb439
+        }
ebb439
+
ebb439
+        add_lb_ct_snat_vip_flows(lb, lb_vip, flow_table);
ebb439
+    }
ebb439
+
ebb439
+    ovn_controller_lb_destroy(lb);
ebb439
+}
ebb439
+
ebb439
+/* Adds OpenFlow flows to flow tables for each Load balancer VIPs and
ebb439
+ * backends to handle the load balanced hairpin traffic. */
ebb439
+static void
ebb439
+add_lb_hairpin_flows(const struct sbrec_load_balancer_table *lb_table,
ebb439
+                     const struct hmap *local_datapaths,
ebb439
+                     struct ovn_desired_flow_table *flow_table)
ebb439
+{
ebb439
+    const struct sbrec_load_balancer *lb;
ebb439
+    SBREC_LOAD_BALANCER_TABLE_FOR_EACH (lb, lb_table) {
ebb439
+        consider_lb_hairpin_flows(lb, local_datapaths, flow_table);
ebb439
+    }
ebb439
+}
ebb439
+
ebb439
 /* Handles neighbor changes in mac_binding table. */
ebb439
 void
ebb439
 lflow_handle_changed_neighbors(
ebb439
@@ -1203,6 +1388,8 @@ lflow_run(struct lflow_ctx_in *l_ctx_in, struct lflow_ctx_out *l_ctx_out)
ebb439
     add_neighbor_flows(l_ctx_in->sbrec_port_binding_by_name,
ebb439
                        l_ctx_in->mac_binding_table, l_ctx_in->local_datapaths,
ebb439
                        l_ctx_out->flow_table);
ebb439
+    add_lb_hairpin_flows(l_ctx_in->lb_table, l_ctx_in->local_datapaths,
ebb439
+                         l_ctx_out->flow_table);
ebb439
 }
ebb439
 
ebb439
 void
ebb439
@@ -1262,6 +1449,15 @@ lflow_add_flows_for_datapath(const struct sbrec_datapath_binding *dp,
ebb439
     dhcp_opts_destroy(&dhcpv6_opts);
ebb439
     nd_ra_opts_destroy(&nd_ra_opts);
ebb439
     controller_event_opts_destroy(&controller_event_opts);
ebb439
+
ebb439
+    /* Add load balancer hairpin flows if the datapath has any load balancers
ebb439
+     * associated. */
ebb439
+    for (size_t i = 0; i < dp->n_load_balancers; i++) {
ebb439
+        consider_lb_hairpin_flows(dp->load_balancers[i],
ebb439
+                                  l_ctx_in->local_datapaths,
ebb439
+                                  l_ctx_out->flow_table);
ebb439
+    }
ebb439
+
ebb439
     return handled;
ebb439
 }
ebb439
 
ebb439
@@ -1279,3 +1475,37 @@ lflow_handle_flows_for_lport(const struct sbrec_port_binding *pb,
ebb439
     return lflow_handle_changed_ref(REF_TYPE_PORTBINDING, pb_ref_name,
ebb439
                                     l_ctx_in, l_ctx_out, &changed);
ebb439
 }
ebb439
+
ebb439
+bool
ebb439
+lflow_handle_changed_lbs(struct lflow_ctx_in *l_ctx_in,
ebb439
+                         struct lflow_ctx_out *l_ctx_out)
ebb439
+{
ebb439
+    const struct sbrec_load_balancer *lb;
ebb439
+
ebb439
+    SBREC_LOAD_BALANCER_TABLE_FOR_EACH_TRACKED (lb, l_ctx_in->lb_table) {
ebb439
+        if (sbrec_load_balancer_is_deleted(lb)) {
ebb439
+            VLOG_DBG("Remove hairpin flows for deleted load balancer "UUID_FMT,
ebb439
+                     UUID_ARGS(&lb->header_.uuid));
ebb439
+            ofctrl_remove_flows(l_ctx_out->flow_table, &lb->header_.uuid);
ebb439
+        }
ebb439
+    }
ebb439
+
ebb439
+    SBREC_LOAD_BALANCER_TABLE_FOR_EACH_TRACKED (lb, l_ctx_in->lb_table) {
ebb439
+        if (sbrec_load_balancer_is_deleted(lb)) {
ebb439
+            continue;
ebb439
+        }
ebb439
+
ebb439
+        if (!sbrec_load_balancer_is_new(lb)) {
ebb439
+            VLOG_DBG("Remove hairpin flows for updated load balancer "UUID_FMT,
ebb439
+                     UUID_ARGS(&lb->header_.uuid));
ebb439
+            ofctrl_remove_flows(l_ctx_out->flow_table, &lb->header_.uuid);
ebb439
+        }
ebb439
+
ebb439
+        VLOG_DBG("Add load balancer hairpin flows for "UUID_FMT,
ebb439
+                 UUID_ARGS(&lb->header_.uuid));
ebb439
+        consider_lb_hairpin_flows(lb, l_ctx_in->local_datapaths,
ebb439
+                                  l_ctx_out->flow_table);
ebb439
+    }
ebb439
+
ebb439
+    return true;
ebb439
+}
ebb439
diff --git a/controller/lflow.h b/controller/lflow.h
ebb439
index 1251fb0f4..1225131de 100644
ebb439
--- a/controller/lflow.h
ebb439
+++ b/controller/lflow.h
ebb439
@@ -68,6 +68,9 @@ struct uuid;
ebb439
 #define OFTABLE_LOG_TO_PHY           65
ebb439
 #define OFTABLE_MAC_BINDING          66
ebb439
 #define OFTABLE_MAC_LOOKUP           67
ebb439
+#define OFTABLE_CHK_LB_HAIRPIN       68
ebb439
+#define OFTABLE_CHK_LB_HAIRPIN_REPLY 69
ebb439
+#define OFTABLE_CT_SNAT_FOR_VIP      70
ebb439
 
ebb439
 /* The number of tables for the ingress and egress pipelines. */
ebb439
 #define LOG_PIPELINE_LEN 24
ebb439
@@ -132,6 +135,7 @@ struct lflow_ctx_in {
ebb439
     const struct sbrec_logical_flow_table *logical_flow_table;
ebb439
     const struct sbrec_multicast_group_table *mc_group_table;
ebb439
     const struct sbrec_chassis *chassis;
ebb439
+    const struct sbrec_load_balancer_table *lb_table;
ebb439
     const struct hmap *local_datapaths;
ebb439
     const struct shash *addr_sets;
ebb439
     const struct shash *port_groups;
ebb439
@@ -160,7 +164,7 @@ void lflow_handle_changed_neighbors(
ebb439
     const struct sbrec_mac_binding_table *,
ebb439
     const struct hmap *local_datapaths,
ebb439
     struct ovn_desired_flow_table *);
ebb439
-
ebb439
+bool lflow_handle_changed_lbs(struct lflow_ctx_in *, struct lflow_ctx_out *);
ebb439
 void lflow_destroy(void);
ebb439
 
ebb439
 void lflow_cache_init(struct hmap *);
ebb439
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
ebb439
index 8d8c678e5..e5479cf3e 100644
ebb439
--- a/controller/ovn-controller.c
ebb439
+++ b/controller/ovn-controller.c
ebb439
@@ -790,7 +790,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
ebb439
     SB_NODE(logical_flow, "logical_flow") \
ebb439
     SB_NODE(dhcp_options, "dhcp_options") \
ebb439
     SB_NODE(dhcpv6_options, "dhcpv6_options") \
ebb439
-    SB_NODE(dns, "dns")
ebb439
+    SB_NODE(dns, "dns") \
ebb439
+    SB_NODE(load_balancer, "load_balancer")
ebb439
 
ebb439
 enum sb_engine_node {
ebb439
 #define SB_NODE(NAME, NAME_STR) SB_##NAME,
ebb439
@@ -1682,6 +1683,10 @@ static void init_lflow_ctx(struct engine_node *node,
ebb439
         (struct sbrec_multicast_group_table *)EN_OVSDB_GET(
ebb439
             engine_get_input("SB_multicast_group", node));
ebb439
 
ebb439
+    struct sbrec_load_balancer_table *lb_table =
ebb439
+        (struct sbrec_load_balancer_table *)EN_OVSDB_GET(
ebb439
+            engine_get_input("SB_load_balancer", node));
ebb439
+
ebb439
     const char *chassis_id = chassis_get_id();
ebb439
     const struct sbrec_chassis *chassis = NULL;
ebb439
     struct ovsdb_idl_index *sbrec_chassis_by_name =
ebb439
@@ -1713,6 +1718,7 @@ static void init_lflow_ctx(struct engine_node *node,
ebb439
     l_ctx_in->logical_flow_table = logical_flow_table;
ebb439
     l_ctx_in->mc_group_table = multicast_group_table;
ebb439
     l_ctx_in->chassis = chassis;
ebb439
+    l_ctx_in->lb_table = lb_table;
ebb439
     l_ctx_in->local_datapaths = &rt_data->local_datapaths;
ebb439
     l_ctx_in->addr_sets = addr_sets;
ebb439
     l_ctx_in->port_groups = port_groups;
ebb439
@@ -2131,6 +2137,23 @@ flow_output_runtime_data_handler(struct engine_node *node,
ebb439
     return true;
ebb439
 }
ebb439
 
ebb439
+static bool
ebb439
+flow_output_sb_load_balancer_handler(struct engine_node *node, void *data)
ebb439
+{
ebb439
+    struct ed_type_runtime_data *rt_data =
ebb439
+        engine_get_input_data("runtime_data", node);
ebb439
+
ebb439
+    struct ed_type_flow_output *fo = data;
ebb439
+    struct lflow_ctx_in l_ctx_in;
ebb439
+    struct lflow_ctx_out l_ctx_out;
ebb439
+    init_lflow_ctx(node, rt_data, fo, &l_ctx_in, &l_ctx_out);
ebb439
+
ebb439
+    bool handled = lflow_handle_changed_lbs(&l_ctx_in, &l_ctx_out);
ebb439
+
ebb439
+    engine_set_node_state(node, EN_UPDATED);
ebb439
+    return handled;
ebb439
+}
ebb439
+
ebb439
 struct ovn_controller_exit_args {
ebb439
     bool *exiting;
ebb439
     bool *restart;
ebb439
@@ -2327,6 +2350,8 @@ main(int argc, char *argv[])
ebb439
     engine_add_input(&en_flow_output, &en_sb_dhcp_options, NULL);
ebb439
     engine_add_input(&en_flow_output, &en_sb_dhcpv6_options, NULL);
ebb439
     engine_add_input(&en_flow_output, &en_sb_dns, NULL);
ebb439
+    engine_add_input(&en_flow_output, &en_sb_load_balancer,
ebb439
+                     flow_output_sb_load_balancer_handler);
ebb439
 
ebb439
     engine_add_input(&en_ct_zones, &en_ovs_open_vswitch, NULL);
ebb439
     engine_add_input(&en_ct_zones, &en_ovs_bridge, NULL);
ebb439
diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
ebb439
index ac6f2f909..0fe5bc3bb 100644
ebb439
--- a/include/ovn/logical-fields.h
ebb439
+++ b/include/ovn/logical-fields.h
ebb439
@@ -57,6 +57,7 @@ enum mff_log_flags_bits {
ebb439
     MLF_LOCAL_ONLY_BIT = 4,
ebb439
     MLF_NESTED_CONTAINER_BIT = 5,
ebb439
     MLF_LOOKUP_MAC_BIT = 6,
ebb439
+    MLF_LOOKUP_LB_HAIRPIN_BIT = 7,
ebb439
 };
ebb439
 
ebb439
 /* MFF_LOG_FLAGS_REG flag assignments */
ebb439
@@ -88,6 +89,8 @@ enum mff_log_flags {
ebb439
 
ebb439
     /* Indicate that the lookup in the mac binding table was successful. */
ebb439
     MLF_LOOKUP_MAC = (1 << MLF_LOOKUP_MAC_BIT),
ebb439
+
ebb439
+    MLF_LOOKUP_LB_HAIRPIN = (1 << MLF_LOOKUP_LB_HAIRPIN_BIT),
ebb439
 };
ebb439
 
ebb439
 /* OVN logical fields
ebb439
diff --git a/tests/ovn.at b/tests/ovn.at
ebb439
index ba17246d4..5cb96bae6 100644
ebb439
--- a/tests/ovn.at
ebb439
+++ b/tests/ovn.at
ebb439
@@ -22727,3 +22727,472 @@ AT_CHECK([test "$encap_rec_mvtep" == "$encap_rec_mvtep1"], [0], [])
ebb439
 
ebb439
 OVN_CLEANUP([hv1])
ebb439
 AT_CLEANUP
ebb439
+
ebb439
+AT_SETUP([ovn -- Load Balancer LS hairpin OF flows])
ebb439
+ovn_start
ebb439
+
ebb439
+net_add n1
ebb439
+
ebb439
+sim_add hv1
ebb439
+as hv1
ebb439
+ovs-vsctl add-br br-phys
ebb439
+ovn_attach n1 br-phys 192.168.0.1
ebb439
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
ebb439
+    set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
ebb439
+    options:tx_pcap=hv1/vif1-tx.pcap \
ebb439
+    options:rxq_pcap=hv1/vif1-rx.pcap \
ebb439
+    ofport-request=1
ebb439
+ovs-vsctl -- add-port br-int hv1-vif2 -- \
ebb439
+    set interface hv1-vif2 external-ids:iface-id=sw1-p1 \
ebb439
+    options:tx_pcap=hv1/vif2-tx.pcap \
ebb439
+    options:rxq_pcap=hv1/vif2-rx.pcap \
ebb439
+    ofport-request=2
ebb439
+
ebb439
+sim_add hv2
ebb439
+as hv2
ebb439
+ovs-vsctl add-br br-phys
ebb439
+ovn_attach n1 br-phys 192.168.0.2
ebb439
+ovs-vsctl -- add-port br-int hv2-vif1 -- \
ebb439
+    set interface hv2-vif1 external-ids:iface-id=sw0-p2 \
ebb439
+    options:tx_pcap=hv2/vif1-tx.pcap \
ebb439
+    options:rxq_pcap=hv2/vif1-rx.pcap \
ebb439
+    ofport-request=1
ebb439
+ovs-vsctl -- add-port br-int hv1-vif2 -- \
ebb439
+    set interface hv1-vif2 external-ids:iface-id=sw1-p2 \
ebb439
+    options:tx_pcap=hv1/vif2-tx.pcap \
ebb439
+    options:rxq_pcap=hv1/vif2-rx.pcap \
ebb439
+    ofport-request=2
ebb439
+
ebb439
+check ovn-nbctl --wait=hv ls-add sw0
ebb439
+check ovn-nbctl lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 00:00:00:00:00:01
ebb439
+
ebb439
+check ovn-nbctl ls-add sw1
ebb439
+check ovn-nbctl lsp-add sw1 sw1-p1 -- lsp-set-addresses sw1-p1 00:00:00:00:01:01
ebb439
+
ebb439
+OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xup])
ebb439
+OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw1-p1) = xup])
ebb439
+
ebb439
+check ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp
ebb439
+check ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp
ebb439
+check ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp
ebb439
+check ovn-nbctl --wait=hv lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68], [0], [dnl
ebb439
+NXST_FLOW reply (xid=0x8):
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69], [0], [dnl
ebb439
+NXST_FLOW reply (xid=0x8):
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70], [0], [dnl
ebb439
+NXST_FLOW reply (xid=0x8):
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68], [0], [dnl
ebb439
+NXST_FLOW reply (xid=0x8):
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69], [0], [dnl
ebb439
+NXST_FLOW reply (xid=0x8):
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70], [0], [dnl
ebb439
+NXST_FLOW reply (xid=0x8):
ebb439
+])
ebb439
+
ebb439
+check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv4-tcp
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 1]
ebb439
+)
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68], [0], [dnl
ebb439
+NXST_FLOW reply (xid=0x8):
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69], [0], [dnl
ebb439
+NXST_FLOW reply (xid=0x8):
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70], [0], [dnl
ebb439
+NXST_FLOW reply (xid=0x8):
ebb439
+])
ebb439
+
ebb439
+check ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.90:8080 42.42.42.42:4041,52.52.52.52:4042 tcp
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 3]
ebb439
+)
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68], [0], [dnl
ebb439
+NXST_FLOW reply (xid=0x8):
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69], [0], [dnl
ebb439
+NXST_FLOW reply (xid=0x8):
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70], [0], [dnl
ebb439
+NXST_FLOW reply (xid=0x8):
ebb439
+])
ebb439
+
ebb439
+check ovn-nbctl lsp-add sw0 sw0-p2
ebb439
+# hv2 should bind sw0-p2 and it should install the LB hairpin flows.
ebb439
+OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xup])
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 3]
ebb439
+)
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
ebb439
+])
ebb439
+
ebb439
+check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv4-udp
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 4]
ebb439
+)
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 4]
ebb439
+)
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
ebb439
+])
ebb439
+
ebb439
+check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv6-tcp
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 5]
ebb439
+)
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 5]
ebb439
+)
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
ebb439
+])
ebb439
+
ebb439
+check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv6-udp
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6]
ebb439
+)
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6]
ebb439
+)
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
ebb439
+])
ebb439
+
ebb439
+check ovn-nbctl --wait=hv ls-lb-add sw1 lb-ipv6-udp
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 7]
ebb439
+)
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 7]
ebb439
+)
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
ebb439
+])
ebb439
+
ebb439
+as hv2 ovs-vsctl del-port hv2-vif1
ebb439
+OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown])
ebb439
+
ebb439
+# Trigger recompute on hv2 as sw0 will not be cleared from local_datapaths.
ebb439
+as hv2 ovn-appctl -t ovn-controller recompute
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0]
ebb439
+)
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | wc -l) -eq 0]
ebb439
+)
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | wc -l) -eq 0]
ebb439
+)
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 7]
ebb439
+)
ebb439
+
ebb439
+check ovn-nbctl --wait=hv lb-del lb-ipv4-tcp
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 4]
ebb439
+)
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0]
ebb439
+)
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
ebb439
+])
ebb439
+
ebb439
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
ebb439
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
ebb439
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
ebb439
+])
ebb439
+
ebb439
+check ovn-nbctl --wait=hv ls-del sw0
ebb439
+check ovn-nbctl --wait=hv ls-del sw1
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0]
ebb439
+)
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | wc -l) -eq 0]
ebb439
+)
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | wc -l) -eq 0]
ebb439
+)
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0]
ebb439
+)
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | wc -l) -eq 0]
ebb439
+)
ebb439
+
ebb439
+OVS_WAIT_UNTIL(
ebb439
+    [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | wc -l) -eq 0]
ebb439
+)
ebb439
+
ebb439
+OVN_CLEANUP([hv1], [hv2])
ebb439
+AT_CLEANUP
ebb439
-- 
ebb439
2.28.0
ebb439