diff --git a/.ovn.metadata b/.ovn.metadata index 21e5af6..a8083bb 100644 --- a/.ovn.metadata +++ b/.ovn.metadata @@ -1,5 +1,5 @@ 002450621b33c5690060345b0aac25bc2426d675 SOURCES/docutils-0.12.tar.gz -d5a30334edfe265936a26db900a8838c83ef4043 SOURCES/openvswitch-0187ead.tar.gz -28d5de798f88964283b144cdd8cb226059d403dd SOURCES/ovn-23.06.1.tar.gz +19829758c492a62983f4536c63e4654e9af934c2 SOURCES/openvswitch-ec1d730.tar.gz +1ad2a2bdb68e5e984310a8519d190350001f433a SOURCES/ovn-23.09.0.tar.gz d34f96421a86004aa5d26ecf975edefd09f948b1 SOURCES/Pygments-1.4.tar.gz 6beb30f18ffac3de7689b7fd63e9a8a7d9c8df3a SOURCES/Sphinx-1.1.3.tar.gz diff --git a/SOURCES/ovn23.06.patch b/SOURCES/ovn23.06.patch deleted file mode 100644 index ccd46e5..0000000 --- a/SOURCES/ovn23.06.patch +++ /dev/null @@ -1,669 +0,0 @@ -diff --git a/NEWS b/NEWS -index ef000ef7b..0056bb175 100644 ---- a/NEWS -+++ b/NEWS -@@ -1,3 +1,6 @@ -+OVN v23.06.2 - xx xxx xxxx -+-------------------------- -+ - OVN v23.06.1 - 29 Aug 2023 - -------------------------- - - Bug fixes -diff --git a/configure.ac b/configure.ac -index fc4ba024a..3ed093ebe 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -13,7 +13,7 @@ - # limitations under the License. - - AC_PREREQ(2.63) --AC_INIT(ovn, 23.06.1, bugs@openvswitch.org) -+AC_INIT(ovn, 23.06.2, bugs@openvswitch.org) - AC_CONFIG_MACRO_DIR([m4]) - AC_CONFIG_AUX_DIR([build-aux]) - AC_CONFIG_HEADERS([config.h]) -diff --git a/controller/lflow.c b/controller/lflow.c -index 22faaf013..718b4a27a 100644 ---- a/controller/lflow.c -+++ b/controller/lflow.c -@@ -2513,10 +2513,10 @@ build_in_port_sec_default_flows(const struct sbrec_port_binding *pb, - * investigation. - * - * Eg. If there are below OF rules in the same table -- * (1) priority=90,icmp6,reg14=0x1,metadata=0x1,nw_ttl=225,icmp_type=135, -+ * (1) priority=90,icmp6,reg14=0x1,metadata=0x1,nw_ttl=255,icmp_type=135, - * icmp_code=0,nd_sll=fa:16:3e:94:05:98 - * actions=load:0->NXM_NX_REG10[12] -- * (2) priority=80,icmp6,reg14=0x1,metadata=0x1,nw_ttl=225,icmp_type=135, -+ * (2) priority=80,icmp6,reg14=0x1,metadata=0x1,nw_ttl=255,icmp_type=135, - * icmp_code=0 actions=load:1->NXM_NX_REG10[12] - * - * An IPv6 NS packet with nd_sll = fa:16:3e:94:05:98 is matching on the -@@ -2801,7 +2801,7 @@ build_in_port_sec_nd_flows(const struct sbrec_port_binding *pb, - reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m); - match_set_dl_type(m, htons(ETH_TYPE_IPV6)); - match_set_nw_proto(m, IPPROTO_ICMPV6); -- match_set_nw_ttl(m, 225); -+ match_set_nw_ttl(m, 255); - match_set_icmp_type(m, 135); - match_set_icmp_code(m, 0); - -diff --git a/controller/ofctrl.c b/controller/ofctrl.c -index 64a444ff6..a1676a788 100644 ---- a/controller/ofctrl.c -+++ b/controller/ofctrl.c -@@ -16,6 +16,7 @@ - #include - #include "bitmap.h" - #include "byte-order.h" -+#include "coverage.h" - #include "dirs.h" - #include "dp-packet.h" - #include "flow.h" -@@ -55,6 +56,8 @@ - - VLOG_DEFINE_THIS_MODULE(ofctrl); - -+COVERAGE_DEFINE(ofctrl_msg_too_long); -+ - /* An OpenFlow flow. */ - struct ovn_flow { - /* Key. */ -@@ -1273,6 +1276,37 @@ ofctrl_add_flow_metered(struct ovn_desired_flow_table *desired_flows, - meter_id, as_info, true); - } - -+struct ofpact_ref { -+ struct hmap_node hmap_node; -+ struct ofpact *ofpact; -+}; -+ -+static struct ofpact_ref * -+ofpact_ref_find(const struct hmap *refs, const struct ofpact *ofpact) -+{ -+ uint32_t hash = hash_bytes(ofpact, ofpact->len, 0); -+ -+ struct ofpact_ref *ref; -+ HMAP_FOR_EACH_WITH_HASH (ref, hmap_node, hash, refs) { -+ if (ofpacts_equal(ref->ofpact, ref->ofpact->len, -+ ofpact, ofpact->len)) { -+ return ref; -+ } -+ } -+ -+ return NULL; -+} -+ -+static void -+ofpact_refs_destroy(struct hmap *refs) -+{ -+ struct ofpact_ref *ref; -+ HMAP_FOR_EACH_POP (ref, hmap_node, refs) { -+ free(ref); -+ } -+ hmap_destroy(refs); -+} -+ - /* Either add a new flow, or append actions on an existing flow. If the - * flow existed, a new link will also be created between the new sb_uuid - * and the existing flow. */ -@@ -1292,6 +1326,21 @@ ofctrl_add_or_append_flow(struct ovn_desired_flow_table *desired_flows, - meter_id); - existing = desired_flow_lookup_conjunctive(desired_flows, &f->flow); - if (existing) { -+ struct hmap existing_conj = HMAP_INITIALIZER(&existing_conj); -+ -+ struct ofpact *ofpact; -+ OFPACT_FOR_EACH (ofpact, existing->flow.ofpacts, -+ existing->flow.ofpacts_len) { -+ if (ofpact->type != OFPACT_CONJUNCTION) { -+ continue; -+ } -+ -+ struct ofpact_ref *ref = xmalloc(sizeof *ref); -+ ref->ofpact = ofpact; -+ uint32_t hash = hash_bytes(ofpact, ofpact->len, 0); -+ hmap_insert(&existing_conj, &ref->hmap_node, hash); -+ } -+ - /* There's already a flow with this particular match and action - * 'conjunction'. Append the action to that flow rather than - * adding a new flow. -@@ -1301,7 +1350,15 @@ ofctrl_add_or_append_flow(struct ovn_desired_flow_table *desired_flows, - ofpbuf_use_stub(&compound, compound_stub, sizeof(compound_stub)); - ofpbuf_put(&compound, existing->flow.ofpacts, - existing->flow.ofpacts_len); -- ofpbuf_put(&compound, f->flow.ofpacts, f->flow.ofpacts_len); -+ -+ OFPACT_FOR_EACH (ofpact, f->flow.ofpacts, f->flow.ofpacts_len) { -+ if (ofpact->type != OFPACT_CONJUNCTION || -+ !ofpact_ref_find(&existing_conj, ofpact)) { -+ ofpbuf_put(&compound, ofpact, OFPACT_ALIGN(ofpact->len)); -+ } -+ } -+ -+ ofpact_refs_destroy(&existing_conj); - - mem_stats.desired_flow_usage -= desired_flow_size(existing); - free(existing->flow.ofpacts); -@@ -1769,6 +1826,18 @@ ovn_flow_log(const struct ovn_flow *f, const char *action) - } - } - -+static void -+ovn_flow_log_size_err(const struct ovn_flow *f) -+{ -+ COVERAGE_INC(ofctrl_msg_too_long); -+ -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); -+ -+ char *s = ovn_flow_to_string(f); -+ VLOG_ERR_RL(&rl, "The FLOW_MOD message is too big: %s", s); -+ free(s); -+} -+ - static void - ovn_flow_uninit(struct ovn_flow *f) - { -@@ -1888,15 +1957,27 @@ encode_bundle_add(struct ofpbuf *msg, struct ofputil_bundle_ctrl_msg *bc) - return ofputil_encode_bundle_add(OFP15_VERSION, &bam); - } - --static void -+static bool - add_flow_mod(struct ofputil_flow_mod *fm, - struct ofputil_bundle_ctrl_msg *bc, - struct ovs_list *msgs) - { - struct ofpbuf *msg = encode_flow_mod(fm); - struct ofpbuf *bundle_msg = encode_bundle_add(msg, bc); -+ -+ uint32_t flow_mod_len = msg->size; -+ uint32_t bundle_len = bundle_msg->size; -+ - ofpbuf_delete(msg); -+ -+ if (flow_mod_len > UINT16_MAX || bundle_len > UINT16_MAX) { -+ ofpbuf_delete(bundle_msg); -+ -+ return false; -+ } -+ - ovs_list_push_back(msgs, &bundle_msg->list_node); -+ return true; - } - - /* group_table. */ -@@ -2235,7 +2316,10 @@ installed_flow_add(struct ovn_flow *d, - .new_cookie = htonll(d->cookie), - .command = OFPFC_ADD, - }; -- add_flow_mod(&fm, bc, msgs); -+ -+ if (!add_flow_mod(&fm, bc, msgs)) { -+ ovn_flow_log_size_err(d); -+ } - } - - static void -@@ -2259,7 +2343,7 @@ installed_flow_mod(struct ovn_flow *i, struct ovn_flow *d, - /* Use OFPFC_ADD so that cookie can be updated. */ - fm.command = OFPFC_ADD; - } -- add_flow_mod(&fm, bc, msgs); -+ bool result = add_flow_mod(&fm, bc, msgs); - - /* Replace 'i''s actions and cookie by 'd''s. */ - mem_stats.installed_flow_usage -= i->ofpacts_len - d->ofpacts_len; -@@ -2267,6 +2351,10 @@ installed_flow_mod(struct ovn_flow *i, struct ovn_flow *d, - i->ofpacts = xmemdup(d->ofpacts, d->ofpacts_len); - i->ofpacts_len = d->ofpacts_len; - i->cookie = d->cookie; -+ -+ if (!result) { -+ ovn_flow_log_size_err(i); -+ } - } - - static void -@@ -2280,7 +2368,10 @@ installed_flow_del(struct ovn_flow *i, - .table_id = i->table_id, - .command = OFPFC_DELETE_STRICT, - }; -- add_flow_mod(&fm, bc, msgs); -+ -+ if (!add_flow_mod(&fm, bc, msgs)) { -+ ovn_flow_log_size_err(i); -+ } - } - - static void -diff --git a/debian/changelog b/debian/changelog -index e1d505a7e..e147d113c 100644 ---- a/debian/changelog -+++ b/debian/changelog -@@ -1,3 +1,9 @@ -+OVN (23.06.2-1) unstable; urgency=low -+ [ OVN team ] -+ * New upstream version -+ -+ -- OVN team Tue, 29 Aug 2023 11:28:59 -0400 -+ - OVN (23.06.1-1) unstable; urgency=low - [ OVN team ] - * New upstream version -diff --git a/northd/northd.c b/northd/northd.c -index af871e260..3d6449c88 100644 ---- a/northd/northd.c -+++ b/northd/northd.c -@@ -259,7 +259,6 @@ enum ovn_stage { - #define REGBIT_LOOKUP_NEIGHBOR_RESULT "reg9[2]" - #define REGBIT_LOOKUP_NEIGHBOR_IP_RESULT "reg9[3]" - #define REGBIT_DST_NAT_IP_LOCAL "reg9[4]" --#define REGBIT_KNOWN_ECMP_NH "reg9[5]" - #define REGBIT_KNOWN_LB_SESSION "reg9[6]" - - /* Register to store the eth address associated to a router port for packets -@@ -367,8 +366,7 @@ enum ovn_stage { - * | | EGRESS_LOOPBACK/ | G | UNUSED | - * | R9 | PKT_LARGER/ | 4 | | - * | | LOOKUP_NEIGHBOR_RESULT/ | | | -- * | | SKIP_LOOKUP_NEIGHBOR/ | | | -- * | | KNOWN_ECMP_NH} | | | -+ * | | SKIP_LOOKUP_NEIGHBOR} | | | - * | | | | | - * | | REG_ORIG_TP_DPORT_ROUTER | | | - * | | | | | -@@ -10326,15 +10324,13 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows, - cidr); - free(cidr); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, 100, -- ds_cstr(&base_match), -- REGBIT_KNOWN_ECMP_NH" = chk_ecmp_nh_mac(); ct_next;", -- &st_route->header_); -+ ds_cstr(&base_match), "ct_next;", -+ &st_route->header_); - - /* And packets that go out over an ECMP route need conntrack */ - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, 100, -- ds_cstr(route_match), -- REGBIT_KNOWN_ECMP_NH" = chk_ecmp_nh(); ct_next;", -- &st_route->header_); -+ ds_cstr(route_match), "ct_next;", -+ &st_route->header_); - - /* Save src eth and inport in ct_label for packets that arrive over - * an ECMP route. -@@ -10347,9 +10343,8 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows, - ds_put_format(&actions, - "ct_commit { ct_label.ecmp_reply_eth = eth.src; " - " %s = %" PRId64 ";}; " -- "commit_ecmp_nh(ipv6 = %s, proto = tcp); next;", -- ct_ecmp_reply_port_match, out_port->sb->tunnel_key, -- IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true"); -+ "next;", -+ ct_ecmp_reply_port_match, out_port->sb->tunnel_key); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100, - ds_cstr(&match), ds_cstr(&actions), - &st_route->header_); -@@ -10360,9 +10355,8 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows, - ds_put_format(&actions, - "ct_commit { ct_label.ecmp_reply_eth = eth.src; " - " %s = %" PRId64 ";}; " -- "commit_ecmp_nh(ipv6 = %s, proto = udp); next;", -- ct_ecmp_reply_port_match, out_port->sb->tunnel_key, -- IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true"); -+ "next;", -+ ct_ecmp_reply_port_match, out_port->sb->tunnel_key); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100, - ds_cstr(&match), ds_cstr(&actions), - &st_route->header_); -@@ -10373,53 +10367,49 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows, - ds_put_format(&actions, - "ct_commit { ct_label.ecmp_reply_eth = eth.src; " - " %s = %" PRId64 ";}; " -- "commit_ecmp_nh(ipv6 = %s, proto = sctp); next;", -- ct_ecmp_reply_port_match, out_port->sb->tunnel_key, -- IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true"); -+ "next;", -+ ct_ecmp_reply_port_match, out_port->sb->tunnel_key); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100, - ds_cstr(&match), ds_cstr(&actions), - &st_route->header_); - - ds_clear(&match); - ds_put_format(&match, -- "%s && (!ct.rpl && ct.est) && tcp && "REGBIT_KNOWN_ECMP_NH" == 0", -+ "%s && (!ct.rpl && ct.est) && tcp", - ds_cstr(&base_match)); - ds_clear(&actions); - ds_put_format(&actions, - "ct_commit { ct_label.ecmp_reply_eth = eth.src; " - " %s = %" PRId64 ";}; " -- "commit_ecmp_nh(ipv6 = %s, proto = tcp); next;", -- ct_ecmp_reply_port_match, out_port->sb->tunnel_key, -- IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true"); -+ "next;", -+ ct_ecmp_reply_port_match, out_port->sb->tunnel_key); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100, - ds_cstr(&match), ds_cstr(&actions), - &st_route->header_); - - ds_clear(&match); - ds_put_format(&match, -- "%s && (!ct.rpl && ct.est) && udp && "REGBIT_KNOWN_ECMP_NH" == 0", -+ "%s && (!ct.rpl && ct.est) && udp", - ds_cstr(&base_match)); - ds_clear(&actions); - ds_put_format(&actions, - "ct_commit { ct_label.ecmp_reply_eth = eth.src; " - " %s = %" PRId64 ";}; " -- "commit_ecmp_nh(ipv6 = %s, proto = udp); next;", -- ct_ecmp_reply_port_match, out_port->sb->tunnel_key, -- IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true"); -+ "next;", -+ ct_ecmp_reply_port_match, out_port->sb->tunnel_key); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100, - ds_cstr(&match), ds_cstr(&actions), - &st_route->header_); - ds_clear(&match); - ds_put_format(&match, -- "%s && (!ct.rpl && ct.est) && sctp && "REGBIT_KNOWN_ECMP_NH" == 0", -+ "%s && (!ct.rpl && ct.est) && sctp", - ds_cstr(&base_match)); - ds_clear(&actions); - ds_put_format(&actions, - "ct_commit { ct_label.ecmp_reply_eth = eth.src; " - " %s = %" PRId64 ";}; " -- "commit_ecmp_nh(ipv6 = %s, proto = sctp); next;", -- ct_ecmp_reply_port_match, out_port->sb->tunnel_key, -- IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true"); -+ "next;", -+ ct_ecmp_reply_port_match, out_port->sb->tunnel_key); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100, - ds_cstr(&match), ds_cstr(&actions), - &st_route->header_); -@@ -10428,7 +10418,7 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows, - * for where to route the packet. - */ - ds_put_format(&ecmp_reply, -- "ct.rpl && "REGBIT_KNOWN_ECMP_NH" == 1 && %s == %"PRId64, -+ "ct.rpl && %s == %"PRId64, - ct_ecmp_reply_port_match, out_port->sb->tunnel_key); - ds_clear(&match); - ds_put_format(&match, "%s && %s", ds_cstr(&ecmp_reply), -@@ -10853,6 +10843,10 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip, - { - bool ipv4 = lb_vip->address_family == AF_INET; - const char *ip_match = ipv4 ? "ip4" : "ip6"; -+ char *aff_action[LROUTER_NAT_LB_FLOW_MAX] = { -+ [LROUTER_NAT_LB_FLOW_SKIP_SNAT] = "flags.skip_snat_for_lb = 1; ", -+ [LROUTER_NAT_LB_FLOW_FORCE_SNAT] = "flags.force_snat_for_lb = 1; ", -+ }; - - int prio = 110; - -@@ -10925,15 +10919,13 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip, - ctx.new_action[LROUTER_NAT_LB_FLOW_SKIP_SNAT] = ds_cstr(&skip_snat_act); - ctx.new_action[LROUTER_NAT_LB_FLOW_FORCE_SNAT] = ds_cstr(&force_snat_act); - -- enum { -- LROUTER_NAT_LB_AFF = LROUTER_NAT_LB_FLOW_MAX, -- LROUTER_NAT_LB_AFF_FORCE_SNAT = LROUTER_NAT_LB_FLOW_MAX + 1, -- }; -- unsigned long *dp_bitmap[LROUTER_NAT_LB_FLOW_MAX + 2]; -+ unsigned long *gw_dp_bitmap[LROUTER_NAT_LB_FLOW_MAX]; -+ unsigned long *aff_dp_bitmap[LROUTER_NAT_LB_FLOW_MAX]; - - size_t bitmap_len = ods_size(lr_datapaths); -- for (size_t i = 0; i < LROUTER_NAT_LB_FLOW_MAX + 2; i++) { -- dp_bitmap[i] = bitmap_allocate(bitmap_len); -+ for (size_t i = 0; i < LROUTER_NAT_LB_FLOW_MAX; i++) { -+ gw_dp_bitmap[i] = bitmap_allocate(bitmap_len); -+ aff_dp_bitmap[i] = bitmap_allocate(bitmap_len); - } - - /* Group gw router since we do not have datapath dependency in -@@ -10954,18 +10946,13 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip, - } - - if (!od->n_l3dgw_ports) { -- bitmap_set1(dp_bitmap[type], index); -+ bitmap_set1(gw_dp_bitmap[type], index); - } else { - build_distr_lrouter_nat_flows_for_lb(&ctx, type, od); - } - - if (lb->affinity_timeout) { -- if (!lport_addresses_is_empty(&od->lb_force_snat_addrs) || -- od->lb_force_snat_router_ip) { -- bitmap_set1(dp_bitmap[LROUTER_NAT_LB_AFF_FORCE_SNAT], index); -- } else { -- bitmap_set1(dp_bitmap[LROUTER_NAT_LB_AFF], index); -- } -+ bitmap_set1(aff_dp_bitmap[type], index); - } - - if (sset_contains(&od->external_ips, lb_vip->vip_str)) { -@@ -10988,33 +10975,20 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip, - - for (size_t type = 0; type < LROUTER_NAT_LB_FLOW_MAX; type++) { - build_gw_lrouter_nat_flows_for_lb(&ctx, type, lr_datapaths, -- dp_bitmap[type]); -+ gw_dp_bitmap[type]); -+ build_lb_affinity_lr_flows(lflows, lb, lb_vip, ds_cstr(match), -+ aff_action[type], aff_dp_bitmap[type], -+ lr_datapaths); - } - -- /* LB affinity flows for datapaths where CMS has specified -- * force_snat_for_lb floag option. -- */ -- build_lb_affinity_lr_flows(lflows, lb, lb_vip, ds_cstr(match), -- "flags.force_snat_for_lb = 1; ", -- dp_bitmap[LROUTER_NAT_LB_AFF_FORCE_SNAT], -- lr_datapaths); -- -- /* LB affinity flows for datapaths where CMS has specified -- * skip_snat_for_lb floag option or regular datapaths. -- */ -- char *lb_aff_action = -- lb->skip_snat ? "flags.skip_snat_for_lb = 1; " : NULL; -- build_lb_affinity_lr_flows(lflows, lb, lb_vip, ds_cstr(match), -- lb_aff_action, dp_bitmap[LROUTER_NAT_LB_AFF], -- lr_datapaths); -- - ds_destroy(&unsnat_match); - ds_destroy(&undnat_match); - ds_destroy(&skip_snat_act); - ds_destroy(&force_snat_act); - -- for (size_t i = 0; i < LROUTER_NAT_LB_FLOW_MAX + 2; i++) { -- bitmap_free(dp_bitmap[i]); -+ for (size_t i = 0; i < LROUTER_NAT_LB_FLOW_MAX; i++) { -+ bitmap_free(gw_dp_bitmap[i]); -+ bitmap_free(aff_dp_bitmap[i]); - } - } - -@@ -11761,7 +11735,13 @@ build_gateway_get_l2_hdr_size(struct ovn_port *op) - struct ovn_port *localnet_port = peer->od->localnet_ports[i]; - const struct nbrec_logical_switch_port *nbsp = localnet_port->nbsp; - -- if (nbsp && nbsp->n_tag_request > 0) { -+ if (!nbsp || !nbsp->tag_request) { -+ continue; -+ } -+ -+ if (nbsp->tag_request[0] || -+ (nbsp->parent_name && nbsp->parent_name[0])) { -+ /* Valid tag. */ - return VLAN_ETH_HEADER_LEN; - } - } -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index 35c79cd69..60cb947c4 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -6193,6 +6193,23 @@ AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" | sort], - table=0 (lr_in_admission ), priority=50 , match=(eth.mcast && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1518); xreg0[[0..47]] = 00:00:20:20:12:13; next;) - ]) - -+# tag 0 requires a parent port -+check ovn-nbctl --wait=sb set Logical_Switch_Port ext-port tag_request=0 -+ -+ovn-sbctl dump-flows lr0 > lr0flows -+AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" | sort], [0], [dnl -+ table=0 (lr_in_admission ), priority=50 , match=(eth.dst == 00:00:20:20:12:13 && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); xreg0[[0..47]] = 00:00:20:20:12:13; next;) -+ table=0 (lr_in_admission ), priority=50 , match=(eth.mcast && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); xreg0[[0..47]] = 00:00:20:20:12:13; next;) -+]) -+ -+check ovn-nbctl --wait=sb set Logical_Switch_Port ext-port parent_name=ext-parent-port -+ -+ovn-sbctl dump-flows lr0 > lr0flows -+AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" | sort], [0], [dnl -+ table=0 (lr_in_admission ), priority=50 , match=(eth.dst == 00:00:20:20:12:13 && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1518); xreg0[[0..47]] = 00:00:20:20:12:13; next;) -+ table=0 (lr_in_admission ), priority=50 , match=(eth.mcast && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1518); xreg0[[0..47]] = 00:00:20:20:12:13; next;) -+]) -+ - AT_CLEANUP - ]) - -@@ -6236,24 +6253,16 @@ AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed 's/192\.168\.0\..0/192. - table=??(lr_in_ip_routing_ecmp), priority=150 , match=(reg8[[0..15]] == 0), action=(next;) - ]) - --AT_CHECK([grep -e "lr_in_ecmp_stateful".*commit_ecmp_nh lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl -- table=??(lr_in_ecmp_stateful), priority=100 , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (!ct.rpl && ct.est) && sctp && reg9[[5]] == 0), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src; ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = sctp); next;) -- table=??(lr_in_ecmp_stateful), priority=100 , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (!ct.rpl && ct.est) && tcp && reg9[[5]] == 0), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src; ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = tcp); next;) -- table=??(lr_in_ecmp_stateful), priority=100 , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (!ct.rpl && ct.est) && udp && reg9[[5]] == 0), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src; ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = udp); next;) -- table=??(lr_in_ecmp_stateful), priority=100 , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (ct.new && !ct.est) && sctp), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src; ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = sctp); next;) -- table=??(lr_in_ecmp_stateful), priority=100 , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (ct.new && !ct.est) && tcp), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src; ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = tcp); next;) -- table=??(lr_in_ecmp_stateful), priority=100 , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (ct.new && !ct.est) && udp), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src; ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = udp); next;) --]) -- --AT_CHECK([grep -e "lr_in_defrag".*chk_ecmp_nh* lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl -- table=??(lr_in_defrag ), priority=100 , match=(inport == "lr0-public" && ip4.src == 1.0.0.1), action=(reg9[[5]] = chk_ecmp_nh_mac(); ct_next;) -- table=??(lr_in_defrag ), priority=100 , match=(reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(reg9[[5]] = chk_ecmp_nh(); ct_next;) -+AT_CHECK([grep -e "lr_in_defrag" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl -+ table=??(lr_in_defrag ), priority=0 , match=(1), action=(next;) -+ table=??(lr_in_defrag ), priority=100 , match=(inport == "lr0-public" && ip4.src == 1.0.0.1), action=(ct_next;) -+ table=??(lr_in_defrag ), priority=100 , match=(reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(ct_next;) - ]) - - dnl The chassis was created with other_config:ct-no-masked-label=false, the flows - dnl should be using ct_label.ecmp_reply_port. - AT_CHECK([grep -e "lr_in_arp_resolve.*ecmp" lr0flows | sed 's/table=../table=??/'], [0], [dnl -- table=??(lr_in_arp_resolve ), priority=200 , match=(ct.rpl && reg9[[5]] == 1 && ct_label.ecmp_reply_port == 1), action=(push(xxreg1); xxreg1 = ct_label; eth.dst = xxreg1[[32..79]]; pop(xxreg1); next;) -+ table=??(lr_in_arp_resolve ), priority=200 , match=(ct.rpl && ct_label.ecmp_reply_port == 1), action=(push(xxreg1); xxreg1 = ct_label; eth.dst = xxreg1[[32..79]]; pop(xxreg1); next;) - ]) - - dnl Simulate an ovn-controller upgrade to a version that supports -@@ -6263,7 +6272,7 @@ check ovn-sbctl set chassis ch1 other_config:ct-no-masked-label=true - check ovn-nbctl --wait=sb sync - ovn-sbctl dump-flows lr0 > lr0flows - AT_CHECK([grep -e "lr_in_arp_resolve.*ecmp" lr0flows | sed 's/table=../table=??/'], [0], [dnl -- table=??(lr_in_arp_resolve ), priority=200 , match=(ct.rpl && reg9[[5]] == 1 && ct_mark.ecmp_reply_port == 1), action=(push(xxreg1); xxreg1 = ct_label; eth.dst = xxreg1[[32..79]]; pop(xxreg1); next;) -+ table=??(lr_in_arp_resolve ), priority=200 , match=(ct.rpl && ct_mark.ecmp_reply_port == 1), action=(push(xxreg1); xxreg1 = ct_label; eth.dst = xxreg1[[32..79]]; pop(xxreg1); next;) - ]) - - # add ecmp route with wrong nexthop -@@ -8807,6 +8816,26 @@ AT_CHECK([grep "lr_in_dnat " R1flows_force_snat | sed 's/table=../table=??/' | s - table=??(lr_in_dnat ), priority=70 , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;) - ]) - -+AS_BOX([Test LR flows - lb_force_snat_ip="172.16.0.1" + skip_snat=true]) -+check ovn-nbctl --wait=sb set logical_router R1 options:lb_force_snat_ip="172.16.0.1" -+check ovn-nbctl --wait=sb set load_balancer lb0 options:skip_snat=true -+ -+ovn-sbctl dump-flows R1 > R1flows_force_skip_snat -+AT_CAPTURE_FILE([R1flows_force_skip_snat]) -+ -+AT_CHECK([grep "lr_in_dnat " R1flows_force_skip_snat | sed 's/table=../table=??/' | sort], [0], [dnl -+ table=??(lr_in_dnat ), priority=0 , match=(1), action=(next;) -+ table=??(lr_in_dnat ), priority=120 , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.16.0.10 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80; skip_snat);) -+ table=??(lr_in_dnat ), priority=150 , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 10.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80; skip_snat);) -+ table=??(lr_in_dnat ), priority=150 , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 20.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=20.0.0.2:80; skip_snat);) -+ table=??(lr_in_dnat ), priority=50 , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;) -+ table=??(lr_in_dnat ), priority=50 , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;) -+ table=??(lr_in_dnat ), priority=70 , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; next;) -+ table=??(lr_in_dnat ), priority=70 , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; next;) -+ table=??(lr_in_dnat ), priority=70 , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;) -+ table=??(lr_in_dnat ), priority=70 , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;) -+]) -+ - AT_CLEANUP - ]) - -diff --git a/tests/ovn.at b/tests/ovn.at -index 48dbc3347..acda8514e 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -34023,10 +34023,10 @@ echo " table=74, priority=80,arp,reg14=0x$sw0p1_key,metadata=0x1 actions=load:0x - table=74, priority=80,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135 actions=load:0->NXM_NX_REG10[[12]] - table=74, priority=80,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136 actions=load:0x1->NXM_NX_REG10[[12]] - table=74, priority=90,arp,reg14=0x$sw0p1_key,metadata=0x1,dl_src=00:00:00:00:00:03,arp_sha=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]" > hv1_t74_flows.expected -+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]] -+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]] -+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]] -+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]" > hv1_t74_flows.expected - - check_port_sec_offlows hv1 74 - -@@ -34060,12 +34060,12 @@ echo " table=74, priority=80,arp,reg14=0x$sw0p1_key,metadata=0x1 actions=load:0x - table=74, priority=80,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136 actions=load:0x1->NXM_NX_REG10[[12]] - table=74, priority=90,arp,reg14=0x$sw0p1_key,metadata=0x1,dl_src=00:00:00:00:00:03,arp_spa=10.0.0.3,arp_sha=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]] - table=74, priority=90,arp,reg14=0x$sw0p1_key,metadata=0x1,dl_src=00:00:00:00:00:13,arp_spa=10.0.0.13,arp_sha=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]" > hv1_t74_flows.expected -+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]] -+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]] -+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]] -+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]] -+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]] -+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]" > hv1_t74_flows.expected - - check_port_sec_offlows hv1 74 - -@@ -34144,13 +34144,13 @@ echo " table=74, priority=80,arp,reg14=0x$sw0p2_key,metadata=0x1 actions=load:0x - table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,dl_src=00:00:00:00:00:04,icmp_type=136,icmp_code=0,nd_target=2000::/64,nd_tll=00:00:00:00:00:04 actions=load:0->NXM_NX_REG10[[12]] - table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,dl_src=00:00:00:00:00:13,icmp_type=136,icmp_code=0,nd_target=aef0::4,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]] - table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,dl_src=00:00:00:00:00:13,icmp_type=136,icmp_code=0,nd_target=aef0::4,nd_tll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:04 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:13,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:13,nd_tll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:4,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]] -- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:4,nd_tll=00:00:00:00:00:04 actions=load:0->NXM_NX_REG10[[12]]" > hv2_t74_flows.expected -+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]] -+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:04 actions=load:0->NXM_NX_REG10[[12]] -+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]] -+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:13,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]] -+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:13,nd_tll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]] -+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:4,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]] -+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:4,nd_tll=00:00:00:00:00:04 actions=load:0->NXM_NX_REG10[[12]]" > hv2_t74_flows.expected - - check_port_sec_offlows hv2 74 - -diff --git a/utilities/checkpatch.py b/utilities/checkpatch.py -index da3224bbc..5467d604d 100755 ---- a/utilities/checkpatch.py -+++ b/utilities/checkpatch.py -@@ -196,7 +196,7 @@ skip_signoff_check = False - # - # Python isn't checked as flake8 performs these checks during build. - line_length_blacklist = re.compile( -- r'\.(am|at|etc|in|m4|mk|patch|py)$|^debian/.*$') -+ r'\.(am|at|etc|in|m4|mk|patch|py|yml)$|^debian/.*$') - - # Don't enforce a requirement that leading whitespace be all spaces on - # files that include these characters in their name, since these kinds diff --git a/SOURCES/ovn23.09.patch b/SOURCES/ovn23.09.patch new file mode 100644 index 0000000..9802b63 --- /dev/null +++ b/SOURCES/ovn23.09.patch @@ -0,0 +1,9867 @@ +diff --git a/.ci/ci.sh b/.ci/ci.sh +index 10f11939c..3f1b41ead 100755 +--- a/.ci/ci.sh ++++ b/.ci/ci.sh +@@ -23,7 +23,7 @@ CONTAINER_WORKDIR="/workspace/ovn-tmp" + IMAGE_NAME=${IMAGE_NAME:-"ovn-org/ovn-tests"} + + # Test variables +-ARCH=${ARCH:-$(uname -i)} ++ARCH=${ARCH:-$(uname -m)} + CC=${CC:-gcc} + + +@@ -105,6 +105,16 @@ function run_tests() { + " + } + ++function check_clang_version_ge() { ++ lower=$1 ++ version=$(clang --version | head -n1 | cut -d' ' -f3) ++ if ! echo -e "$lower\n$version" | sort -CV; then ++ return 1 ++ fi ++ ++ return 0 ++} ++ + options=$(getopt --options "" \ + --long help,shell,archive-logs,jobs:,ovn-path:,ovs-path:,image-name:\ + -- "${@}") +@@ -149,7 +159,7 @@ while true; do + done + + # Workaround for https://bugzilla.redhat.com/2153359 +-if [ "$ARCH" = "aarch64" ]; then ++if [ "$ARCH" = "aarch64" ] && ! check_clang_version_ge "16.0.0"; then + ASAN_OPTIONS="detect_leaks=0" + fi + +diff --git a/.ci/linux-build.sh b/.ci/linux-build.sh +index 5a79a52da..7270b9e60 100755 +--- a/.ci/linux-build.sh ++++ b/.ci/linux-build.sh +@@ -80,7 +80,7 @@ function configure_gcc() + # We should install gcc-multilib for x86 build, we cannot + # do it directly because gcc-multilib is not available + # for arm64 +- sudo apt install -y gcc-multilib ++ sudo apt update && sudo apt install -y gcc-multilib + fi + fi + } +diff --git a/.ci/linux-util.sh b/.ci/linux-util.sh +new file mode 100755 +index 000000000..920345123 +--- /dev/null ++++ b/.ci/linux-util.sh +@@ -0,0 +1,19 @@ ++#!/bin/bash ++ ++function free_up_disk_space_ubuntu() ++{ ++ local pkgs='azure-cli aspnetcore-* dotnet-* ghc-* firefox* ++ google-chrome-stable google-cloud-cli libmono-* llvm-* ++ microsoft-edge-stable mono-* msbuild mysql-server-core-* ++ php-* php7* powershell* temurin-* zulu-*' ++ ++ # Use apt patterns to only select real packages that match the names ++ # in the list above. ++ local pkgs=$(echo $pkgs | sed 's/[^ ]* */~n&/g') ++ ++ sudo apt update && sudo apt-get --auto-remove -y purge $pkgs ++ ++ local paths='/usr/local/lib/android/ /usr/share/dotnet/ /opt/ghc/ ++ /usr/local/share/boost/' ++ sudo rm -rf $paths ++} +diff --git a/.ci/ovn-kubernetes/Dockerfile b/.ci/ovn-kubernetes/Dockerfile +index 12f819017..eda8b6d02 100644 +--- a/.ci/ovn-kubernetes/Dockerfile ++++ b/.ci/ovn-kubernetes/Dockerfile +@@ -81,6 +81,7 @@ RUN dnf install -y *.rpm && rm -f *.rpm + # install ovn-kubernetes binaries built in previous stage + RUN mkdir -p /usr/libexec/cni/ + COPY --from=ovnkubebuilder /root/ovn-kubernetes/go-controller/_output/go/bin/ovnkube /usr/bin/ ++COPY --from=ovnkubebuilder /root/ovn-kubernetes/go-controller/_output/go/bin/ovnkube-identity /usr/bin/ + COPY --from=ovnkubebuilder /root/ovn-kubernetes/go-controller/_output/go/bin/ovn-kube-util /usr/bin/ + COPY --from=ovnkubebuilder /root/ovn-kubernetes/go-controller/_output/go/bin/ovndbchecker /usr/bin/ + COPY --from=ovnkubebuilder /root/ovn-kubernetes/go-controller/_output/go/bin/ovn-k8s-cni-overlay /usr/libexec/cni/ovn-k8s-cni-overlay +diff --git a/.cirrus.yml b/.cirrus.yml +index bd4cd08aa..dd2aa1bfd 100644 +--- a/.cirrus.yml ++++ b/.cirrus.yml +@@ -1,14 +1,33 @@ +-arm_unit_tests_task: ++compute_engine_instance: ++ image_project: ubuntu-os-cloud ++ image: family/ubuntu-2304-arm64 ++ architecture: arm64 ++ platform: linux ++ memory: 4G ++ ++# Run separate task for the image build, so it's running only once outside ++# the test matrix. ++build_image_task: ++ install_dependencies_script: ++ - sudo apt update ++ - sudo apt install -y podman make ++ ++ build_container_script: ++ - cd utilities/containers ++ - make ubuntu ++ - podman save -o /tmp/image.tar ovn-org/ovn-tests:ubuntu ++ ++ upload_image_script: ++ - curl -s -X POST -T /tmp/image.tar http://$CIRRUS_HTTP_CACHE_HOST/${CIRRUS_CHANGE_IN_REPO} + +- arm_container: +- image: ghcr.io/ovn-org/ovn-tests:fedora +- memory: 4G +- cpu: 2 ++arm_unit_tests_task: ++ depends_on: ++ - build_image + + env: +- ARCH: aarch64 + CIRRUS_CLONE_SUBMODULES: true + PATH: ${HOME}/bin:${HOME}/.local/bin:${PATH} ++ IMAGE_NAME: ovn-org/ovn-tests:ubuntu + matrix: + - CC: gcc + TESTSUITE: test +@@ -31,5 +50,16 @@ arm_unit_tests_task: + + name: ARM64 ${CC} ${TESTSUITE} ${TEST_RANGE} + ++ install_dependencies_script: ++ - sudo apt update ++ - sudo apt install -y podman ++ ++ download_cache_script: ++ - curl http://$CIRRUS_HTTP_CACHE_HOST/${CIRRUS_CHANGE_IN_REPO} -o /tmp/image.tar ++ ++ load_image_script: ++ - podman load -i /tmp/image.tar ++ - rm -rf /tmp/image.tar ++ + build_script: +- - ./.ci/linux-build.sh ++ - ./.ci/ci.sh --archive-logs +diff --git a/.github/workflows/containers.yml b/.github/workflows/containers.yml +index 57e815ed8..bdd118087 100644 +--- a/.github/workflows/containers.yml ++++ b/.github/workflows/containers.yml +@@ -15,7 +15,7 @@ env: + + jobs: + container: +- runs-on: ubuntu-latest ++ runs-on: ubuntu-22.04 + strategy: + matrix: + distro: [ fedora, ubuntu ] +diff --git a/.github/workflows/ovn-fake-multinode-tests.yml b/.github/workflows/ovn-fake-multinode-tests.yml +index 75c5ca818..25610df53 100644 +--- a/.github/workflows/ovn-fake-multinode-tests.yml ++++ b/.github/workflows/ovn-fake-multinode-tests.yml +@@ -13,7 +13,7 @@ concurrency: + jobs: + build: + name: Build ovn-fake-multinode image +- runs-on: ubuntu-latest ++ runs-on: ubuntu-22.04 + strategy: + matrix: + cfg: +@@ -69,7 +69,7 @@ jobs: + path: /tmp/_output/ovn_${{ matrix.cfg.branch }}_image.tar + + multinode-tests: +- runs-on: ubuntu-latest ++ runs-on: ubuntu-22.04 + timeout-minutes: 15 + needs: [build] + strategy: +@@ -99,13 +99,18 @@ jobs: + XDG_RUNTIME_DIR: '' + + steps: ++ - name: Check out ovn ++ uses: actions/checkout@v3 ++ + - name: install required dependencies + run: | + sudo apt update || true + sudo apt install -y ${{ env.dependencies }} + + - name: Free up disk space +- run: sudo eatmydata apt-get remove --auto-remove -y aspnetcore-* dotnet-* libmono-* mono-* msbuild php-* php7* ghc-* zulu-* ++ run: | ++ . .ci/linux-util.sh ++ free_up_disk_space_ubuntu + + - uses: actions/download-artifact@v3 + with: +@@ -153,7 +158,7 @@ jobs: + - name: set up python + uses: actions/setup-python@v4 + with: +- python-version: '3.x' ++ python-version: '3.12' + + - name: Check out ovn + uses: actions/checkout@v3 +diff --git a/.github/workflows/ovn-kubernetes.yml b/.github/workflows/ovn-kubernetes.yml +index 69ab0566d..1689396d6 100644 +--- a/.github/workflows/ovn-kubernetes.yml ++++ b/.github/workflows/ovn-kubernetes.yml +@@ -24,7 +24,7 @@ env: + jobs: + build: + name: Build +- runs-on: ubuntu-latest ++ runs-on: ubuntu-22.04 + steps: + - name: Enable Docker experimental features + run: | +@@ -62,7 +62,7 @@ jobs: + e2e: + name: e2e + if: github.event_name != 'schedule' +- runs-on: ubuntu-latest ++ runs-on: ubuntu-22.04 + timeout-minutes: 220 + strategy: + fail-fast: false +@@ -95,18 +95,14 @@ jobs: + KIND_IPV6_SUPPORT: "${{ matrix.ipfamily == 'IPv6' || matrix.ipfamily == 'dualstack' }}" + steps: + +- - name: Free up disk space +- run: | +- sudo eatmydata apt-get purge --auto-remove -y \ +- azure-cli aspnetcore-* dotnet-* ghc-* firefox \ +- google-chrome-stable google-cloud-sdk \ +- llvm-* microsoft-edge-stable mono-* \ +- msbuild mysql-server-core-* php-* php7* \ +- powershell temurin-* zulu-* +- + - name: Check out ovn + uses: actions/checkout@v3 + ++ - name: Free up disk space ++ run: | ++ . .ci/linux-util.sh ++ free_up_disk_space_ubuntu ++ + - name: Check out ovn-kubernetes + uses: actions/checkout@v3 + with: +diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml +index fe2a14c40..aa01dc7e9 100644 +--- a/.github/workflows/test.yml ++++ b/.github/workflows/test.yml +@@ -80,10 +80,62 @@ jobs: + if: steps.dpdk_cache.outputs.cache-hit != 'true' + run: ./.ci/dpdk-build.sh + ++ prepare-container: ++ # This job has the following matrix, x: Job trigger, y: Branch ++ # (scheduled jobs run only on main): ++ # +-------+-------------------+-------------------+ ++ # | | Push/Pull request | Scheduled | ++ # +-------+-------------------+-------------------+ ++ # | main | ghcr.io - Ubuntu | ghcr.io - Fedora | ++ # +-------+-------------------+-------------------+ ++ # | !main | Builds - Ubuntu | xxxxxxxxxxxxxxxxx | ++ # +-------+-------------------+-------------------+ ++ env: ++ DEPENDENCIES: podman ++ name: Prepare container ++ runs-on: ubuntu-22.04 ++ ++ steps: ++ - uses: actions/checkout@v3 ++ ++ - name: Update APT cache ++ run: sudo apt update ++ ++ - name: Install dependencies ++ run: sudo apt install -y ${{ env.DEPENDENCIES }} ++ ++ - name: Choose image distro ++ if: github.event_name == 'push' || github.event_name == 'pull_request' ++ run: | ++ echo "IMAGE_DISTRO=ubuntu" >> $GITHUB_ENV ++ ++ - name: Choose image distro ++ if: github.event_name == 'schedule' ++ run: | ++ echo "IMAGE_DISTRO=fedora" >> $GITHUB_ENV ++ ++ - name: Build container ++ if: github.ref_name != 'main' ++ run: make ${{ env.IMAGE_DISTRO }} ++ working-directory: utilities/containers ++ ++ - name: Download container ++ if: github.ref_name == 'main' ++ run: podman pull ghcr.io/ovn-org/ovn-tests:${{ env.IMAGE_DISTRO }} ++ ++ - name: Export image ++ run: podman save -o /tmp/image.tar ovn-org/ovn-tests ++ ++ - name: Cache image ++ id: image_cache ++ uses: actions/cache@v3 ++ with: ++ path: /tmp/image.tar ++ key: ${{ github.sha }} ++ + build-linux: +- needs: build-dpdk ++ needs: [build-dpdk, prepare-container] + env: +- IMAGE_NAME: ghcr.io/ovn-org/ovn-tests:ubuntu + ARCH: ${{ matrix.cfg.arch }} + CC: ${{ matrix.cfg.compiler }} + DPDK: ${{ matrix.cfg.dpdk }} +@@ -94,7 +146,7 @@ jobs: + SANITIZERS: ${{ matrix.cfg.sanitizers }} + + name: linux ${{ join(matrix.cfg.*, ' ') }} +- runs-on: ubuntu-latest ++ runs-on: ubuntu-22.04 + + strategy: + fail-fast: false +@@ -157,13 +209,25 @@ jobs: + sort -V | tail -1) + working-directory: ovs + +- - name: cache ++ - name: cache dpdk + if: matrix.cfg.dpdk != '' + uses: actions/cache@v3 + with: + path: dpdk-dir + key: ${{ needs.build-dpdk.outputs.dpdk_key }} + ++ - name: image cache ++ uses: actions/cache@v3 ++ with: ++ path: /tmp/image.tar ++ key: ${{ github.sha }} ++ ++ - name: load image ++ run: | ++ sudo podman load -i /tmp/image.tar ++ podman load -i /tmp/image.tar ++ rm -rf /tmp/image.tar ++ + - name: build + if: ${{ startsWith(matrix.cfg.testsuite, 'system-test') }} + run: sudo -E ./.ci/ci.sh --archive-logs +@@ -225,7 +289,7 @@ jobs: + - name: set up python + uses: actions/setup-python@v4 + with: +- python-version: '3.x' ++ python-version: '3.12' + - name: prepare + run: ./.ci/osx-prepare.sh + - name: build +@@ -239,8 +303,8 @@ jobs: + + build-linux-rpm: + name: linux rpm fedora +- runs-on: ubuntu-latest +- container: fedora:latest ++ runs-on: ubuntu-22.04 ++ container: fedora:38 + timeout-minutes: 30 + + strategy: +diff --git a/.readthedocs.yaml b/.readthedocs.yaml +new file mode 100644 +index 000000000..8c451663a +--- /dev/null ++++ b/.readthedocs.yaml +@@ -0,0 +1,26 @@ ++# .readthedocs.yaml ++# Read the Docs configuration file. ++# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details. ++ ++# Required. ++version: 2 ++ ++# Set the OS, Python version, etc. ++build: ++ os: ubuntu-22.04 ++ tools: ++ python: "3.12" ++ ++# Build documentation in the "Documentation/" directory with Sphinx. ++sphinx: ++ configuration: Documentation/conf.py ++ # Default HTML builder. ++ builder: "html" ++ ++# Build all formats: HTML, PDF, ePub. ++formats: all ++ ++# Declare the Python requirements. ++python: ++ install: ++ - requirements: Documentation/requirements.txt +diff --git a/Documentation/conf.py b/Documentation/conf.py +index f7eceaec8..f8fc0125f 100644 +--- a/Documentation/conf.py ++++ b/Documentation/conf.py +@@ -12,16 +12,14 @@ + # All configuration values have a default; values that are commented out + # serve to show the default. + ++import importlib + import string + import sys + +-try: +- import ovs_sphinx_theme +- use_ovs_theme = True +-except ImportError: +- print("Cannot find 'ovs-sphinx-theme' package. " ++use_rtd_theme = importlib.util.find_spec('sphinx_rtd_theme') is not None ++if not use_rtd_theme: ++ print("Cannot find 'sphinx_rtd_theme' package. " + "Falling back to default theme.") +- use_ovs_theme = False + + # -- General configuration ------------------------------------------------ + +@@ -48,7 +46,7 @@ master_doc = 'contents' + + # General information about the project. + project = u'Open Virtual Network (OVN)' +-copyright = u'2020, The Open Virtual Network (OVN) Development Community' ++copyright = u'2020-2023, The Open Virtual Network (OVN) Development Community' + author = u'The Open Virtual Network (OVN) Development Community' + + # The version info for the project you're documenting, acts as replacement for +@@ -89,14 +87,8 @@ linkcheck_anchors = False + # The theme to use for HTML and HTML Help pages. See the documentation for + # a list of builtin themes. + # +-if use_ovs_theme: +- html_theme = 'ovs' +- +-# Add any paths that contain custom themes here, relative to this directory. +-if use_ovs_theme: +- html_theme_path = [ovs_sphinx_theme.get_theme_dir()] +-else: +- html_theme_path = [] ++if use_rtd_theme: ++ html_theme = 'sphinx_rtd_theme' + + # The name of an image file (relative to this directory) to place at the top + # of the sidebar. +diff --git a/Documentation/internals/documentation.rst b/Documentation/internals/documentation.rst +index 72a120bef..59c18b3a2 100644 +--- a/Documentation/internals/documentation.rst ++++ b/Documentation/internals/documentation.rst +@@ -41,17 +41,13 @@ variety of other output formats but also allows for things like + cross-referencing and indexing. for more information on the two, refer to the + :doc:`contributing/documentation-style`. + +-ovs-sphinx-theme ++sphinx_rtd_theme + ---------------- + +-The documentation uses its own theme, `ovs-sphinx-theme`, which can be found on +-GitHub__ and is published on pypi__. This is shared by Open vSwitch and OVN. +-It is packaged separately to ensure all documentation gets the latest version +-of the theme (assuming there are no major version bumps in that package). If +-building locally and the package is installed, it will be used. If the package +-is not installed, Sphinx will fallback to the default theme. +- +-The package is currently maintained by Stephen Finucane and Russell Bryant. ++The documentation uses `sphinx_rtd_theme`, which can be found on GitHub__ and ++is published on pypi__. It is also packaged in major distributions. ++If building locally and the package is installed, it will be used. If the ++package is not installed, Sphinx will fallback to the default theme. + + Read the Docs + ------------- +@@ -72,6 +68,6 @@ modifications to this site, refer to the `GitHub project`__. + + __ http://docutils.sourceforge.net/rst.html + __ http://www.sphinx-doc.org/ +-__ https://github.com/openvswitch/ovs-sphinx-theme +-__ https://pypi.python.org/pypi/ovs-sphinx-theme ++__ https://github.com/readthedocs/sphinx_rtd_theme ++__ https://pypi.python.org/pypi/sphinx_rtd_theme + __ https://github.com/ovn-org/ovn-org.github.io +diff --git a/Documentation/internals/release-process.rst b/Documentation/internals/release-process.rst +index ec79fe48c..26d3f8d4d 100644 +--- a/Documentation/internals/release-process.rst ++++ b/Documentation/internals/release-process.rst +@@ -64,6 +64,10 @@ Scheduling`_ for the timing of each stage: + branch. Features to be added to release branches should be limited in scope + and risk and discussed on ovs-dev before creating the branch. + ++ In order to keep the CI stable on the new release branch, the Ubuntu ++ container should be pinned to the current LTS version in the Dockerfile ++ e.g. registry.hub.docker.com/library/ubuntu:22.04. ++ + 3. When committers come to rough consensus that the release is ready, they + release the .0 release on its branch, e.g. 25.09.0 for branch-25.09. To + make the actual release, a committer pushes a signed tag named, e.g. +diff --git a/Documentation/intro/install/general.rst b/Documentation/intro/install/general.rst +index 589518846..dd8bf5c2c 100644 +--- a/Documentation/intro/install/general.rst ++++ b/Documentation/intro/install/general.rst +@@ -134,10 +134,7 @@ following to obtain better warnings: + + - clang, version 3.4 or later + +-- flake8 along with the hacking flake8 plugin (for Python code). The automatic +- flake8 check that runs against Python code has some warnings enabled that +- come from the "hacking" flake8 plugin. If it's not installed, the warnings +- just won't occur until it's run on a system with "hacking" installed. ++- flake8 (for Python code) + + You may find the ovs-dev script found in ``ovs/utilities/ovs-dev.py`` useful. + +diff --git a/Documentation/requirements.txt b/Documentation/requirements.txt +index 77130c6e0..63a7997f7 100644 +--- a/Documentation/requirements.txt ++++ b/Documentation/requirements.txt +@@ -1,2 +1,2 @@ +-sphinx>=1.1,<2.0 +-ovs_sphinx_theme>=1.0,<1.1 ++sphinx>=1.1 ++sphinx_rtd_theme>=1.0,<2.0 +diff --git a/Makefile.am b/Makefile.am +index b58d4a501..bfc9565e8 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -90,6 +90,7 @@ EXTRA_DIST = \ + .ci/dpdk-build.sh \ + .ci/dpdk-prepare.sh \ + .ci/linux-build.sh \ ++ .ci/linux-util.sh \ + .ci/osx-build.sh \ + .ci/osx-prepare.sh \ + .ci/ovn-kubernetes/Dockerfile \ +@@ -99,6 +100,7 @@ EXTRA_DIST = \ + .github/workflows/test.yml \ + .github/workflows/ovn-kubernetes.yml \ + .github/workflows/ovn-fake-multinode-tests.yml \ ++ .readthedocs.yaml \ + boot.sh \ + $(MAN_FRAGMENTS) \ + $(MAN_ROOTS) \ +@@ -414,16 +416,10 @@ ALL_LOCAL += flake8-check + # F811 redefinition of unused from line (only from flake8 v2.0) + # D*** -- warnings from flake8-docstrings plugin + # H*** -- warnings from flake8 hacking plugin (custom style checks beyond PEP8) +-# H231 Python 3.x incompatible 'except x,y:' construct +-# H232 Python 3.x incompatible octal 077 should be written as 0o77 +-# H233 Python 3.x incompatible use of print operator +-# H238 old style class declaration, use new style (inherit from `object`) +-FLAKE8_SELECT = H231,H232,H233,H238 + FLAKE8_IGNORE = E121,E123,E125,E126,E127,E128,E129,E131,E722,W503,W504,F811,D,H,I + flake8-check: $(FLAKE8_PYFILES) + $(FLAKE8_WERROR)$(AM_V_GEN) \ + src='$^' && \ +- flake8 $$src --select=$(FLAKE8_SELECT) $(FLAKE8_FLAGS) && \ + flake8 $$src --ignore=$(FLAKE8_IGNORE) $(FLAKE8_FLAGS) && \ + touch $@ + endif +diff --git a/NEWS b/NEWS +index 75e046ab3..ed74d0f38 100644 +--- a/NEWS ++++ b/NEWS +@@ -1,3 +1,11 @@ ++OVN v23.09.2 - xx xxx xxxx ++-------------------------- ++ ++ ++OVN v23.09.1 - 01 Dec 2023 ++-------------------------- ++ - Bug fixes ++ + OVN v23.09.0 - 15 Sep 2023 + ---------------------------- + - Added FDB aging mechanism, that is disabled by default. +diff --git a/configure.ac b/configure.ac +index ab404b959..e4d5134dd 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -13,7 +13,7 @@ + # limitations under the License. + + AC_PREREQ(2.63) +-AC_INIT(ovn, 23.09.0, bugs@openvswitch.org) ++AC_INIT(ovn, 23.09.2, bugs@openvswitch.org) + AC_CONFIG_MACRO_DIR([m4]) + AC_CONFIG_AUX_DIR([build-aux]) + AC_CONFIG_HEADERS([config.h]) +diff --git a/controller-vtep/ovn-controller-vtep.c b/controller-vtep/ovn-controller-vtep.c +index 23b368179..4472e5285 100644 +--- a/controller-vtep/ovn-controller-vtep.c ++++ b/controller-vtep/ovn-controller-vtep.c +@@ -306,10 +306,12 @@ parse_options(int argc, char *argv[]) + + switch (c) { + case 'd': ++ free(ovnsb_remote); + ovnsb_remote = xstrdup(optarg); + break; + + case 'D': ++ free(vtep_remote); + vtep_remote = xstrdup(optarg); + break; + +diff --git a/controller/binding.c b/controller/binding.c +index a521f2828..2afc5d48a 100644 +--- a/controller/binding.c ++++ b/controller/binding.c +@@ -55,8 +55,13 @@ struct claimed_port { + long long int last_claimed; + }; + ++struct qos_port { ++ bool added; ++}; ++ + static struct shash _claimed_ports = SHASH_INITIALIZER(&_claimed_ports); + static struct sset _postponed_ports = SSET_INITIALIZER(&_postponed_ports); ++static struct shash _qos_ports = SHASH_INITIALIZER(&_qos_ports); + + static void + remove_additional_chassis(const struct sbrec_port_binding *pb, +@@ -218,6 +223,17 @@ get_qos_egress_port_interface(struct shash *bridge_mappings, + return NULL; + } + ++static void ++add_or_del_qos_port(const char *ovn_port, bool add) ++{ ++ struct qos_port *qos_port = shash_find_data(&_qos_ports, ovn_port); ++ if (!qos_port) { ++ qos_port = xzalloc(sizeof *qos_port); ++ shash_add(&_qos_ports, ovn_port, qos_port); ++ } ++ qos_port->added = add; ++} ++ + /* 34359738360 == (2^32 - 1) * 8. netdev_set_qos() doesn't support + * 64-bit rate netlink attributes, so the maximum value is 2^32 - 1 + * bytes. The 'max-rate' config option is in bits, so multiplying by 8. +@@ -225,7 +241,7 @@ get_qos_egress_port_interface(struct shash *bridge_mappings, + * can be unrecognized for certain NICs or reported too low for virtual + * interfaces. */ + #define OVN_QOS_MAX_RATE 34359738360ULL +-static void ++static bool + add_ovs_qos_table_entry(struct ovsdb_idl_txn *ovs_idl_txn, + const struct ovsrec_port *port, + unsigned long long min_rate, +@@ -239,7 +255,7 @@ add_ovs_qos_table_entry(struct ovsdb_idl_txn *ovs_idl_txn, + const struct ovsrec_qos *qos = port->qos; + if (qos && !smap_get_bool(&qos->external_ids, "ovn_qos", false)) { + /* External configured QoS, do not overwrite it. */ +- return; ++ return false; + } + + if (!qos) { +@@ -282,22 +298,18 @@ add_ovs_qos_table_entry(struct ovsdb_idl_txn *ovs_idl_txn, + ovsrec_queue_verify_external_ids(queue); + ovsrec_queue_set_external_ids(queue, &external_ids); + smap_destroy(&external_ids); ++ return true; + } + + static void +-remove_stale_qos_entry(struct ovsdb_idl_txn *ovs_idl_txn, +- const struct sbrec_port_binding *pb, ++remove_stale_qos_entry( const char *logical_port, + struct ovsdb_idl_index *ovsrec_port_by_qos, + const struct ovsrec_qos_table *qos_table, + struct hmap *queue_map) + { +- if (!ovs_idl_txn) { +- return; +- } +- + struct qos_queue *q = find_qos_queue( +- queue_map, hash_string(pb->logical_port, 0), +- pb->logical_port); ++ queue_map, hash_string(logical_port, 0), ++ logical_port); + if (!q) { + return; + } +@@ -338,8 +350,12 @@ remove_stale_qos_entry(struct ovsdb_idl_txn *ovs_idl_txn, + + static void + configure_qos(const struct sbrec_port_binding *pb, +- struct binding_ctx_in *b_ctx_in, +- struct binding_ctx_out *b_ctx_out) ++ struct ovsdb_idl_txn *ovs_idl_txn, ++ struct ovsdb_idl_index *ovsrec_port_by_qos, ++ const struct ovsrec_qos_table *qos_table, ++ struct hmap *qos_map, ++ const struct ovsrec_open_vswitch_table *ovs_table, ++ const struct ovsrec_bridge_table *bridge_table) + { + unsigned long long min_rate = smap_get_ullong( + &pb->options, "qos_min_rate", 0); +@@ -351,20 +367,20 @@ configure_qos(const struct sbrec_port_binding *pb, + + if ((!min_rate && !max_rate && !burst) || !queue_id) { + /* Qos is not configured for this port. */ +- remove_stale_qos_entry(b_ctx_in->ovs_idl_txn, pb, +- b_ctx_in->ovsrec_port_by_qos, +- b_ctx_in->qos_table, b_ctx_out->qos_map); ++ remove_stale_qos_entry(pb->logical_port, ++ ovsrec_port_by_qos, ++ qos_table, qos_map); + return; + } + + const char *network = smap_get(&pb->options, "qos_physical_network"); + uint32_t hash = hash_string(pb->logical_port, 0); +- struct qos_queue *q = find_qos_queue(b_ctx_out->qos_map, hash, ++ struct qos_queue *q = find_qos_queue(qos_map, hash, + pb->logical_port); + if (!q || q->min_rate != min_rate || q->max_rate != max_rate || + q->burst != burst || (network && strcmp(network, q->network))) { + struct shash bridge_mappings = SHASH_INITIALIZER(&bridge_mappings); +- add_ovs_bridge_mappings(b_ctx_in->ovs_table, b_ctx_in->bridge_table, ++ add_ovs_bridge_mappings(ovs_table, bridge_table, + &bridge_mappings); + + const struct ovsrec_port *port = NULL; +@@ -375,25 +391,58 @@ configure_qos(const struct sbrec_port_binding *pb, + } + if (iface) { + /* Add new QoS entries. */ +- add_ovs_qos_table_entry(b_ctx_in->ovs_idl_txn, port, min_rate, ++ if (add_ovs_qos_table_entry(ovs_idl_txn, port, min_rate, + max_rate, burst, queue_id, +- pb->logical_port); +- if (!q) { +- q = xzalloc(sizeof *q); +- hmap_insert(b_ctx_out->qos_map, &q->node, hash); +- q->port = xstrdup(pb->logical_port); +- q->queue_id = queue_id; ++ pb->logical_port)) { ++ if (!q) { ++ q = xzalloc(sizeof *q); ++ hmap_insert(qos_map, &q->node, hash); ++ q->port = xstrdup(pb->logical_port); ++ q->queue_id = queue_id; ++ } ++ free(q->network); ++ q->network = network ? xstrdup(network) : NULL; ++ q->min_rate = min_rate; ++ q->max_rate = max_rate; ++ q->burst = burst; + } +- free(q->network); +- q->network = network ? xstrdup(network) : NULL; +- q->min_rate = min_rate; +- q->max_rate = max_rate; +- q->burst = burst; + } + shash_destroy(&bridge_mappings); + } + } + ++void ++update_qos(struct ovsdb_idl_index *sbrec_port_binding_by_name, ++ struct ovsdb_idl_txn *ovs_idl_txn, ++ struct ovsdb_idl_index *ovsrec_port_by_qos, ++ const struct ovsrec_qos_table *qos_table, ++ struct hmap *qos_map, ++ const struct ovsrec_open_vswitch_table *ovs_table, ++ const struct ovsrec_bridge_table *bridge_table) ++{ ++ /* Remove qos for any ports for which we could not do it before */ ++ const struct sbrec_port_binding *pb; ++ ++ struct shash_node *node; ++ SHASH_FOR_EACH_SAFE (node, &_qos_ports) { ++ struct qos_port *qos_port = (struct qos_port *) node->data; ++ if (qos_port->added) { ++ pb = lport_lookup_by_name(sbrec_port_binding_by_name, ++ node->name); ++ if (pb) { ++ configure_qos(pb, ovs_idl_txn, ovsrec_port_by_qos, qos_table, ++ qos_map, ovs_table, bridge_table); ++ } ++ } else { ++ remove_stale_qos_entry(node->name, ++ ovsrec_port_by_qos, ++ qos_table, qos_map); ++ } ++ free(qos_port); ++ shash_delete(&_qos_ports, node); ++ } ++} ++ + static const struct ovsrec_queue * + find_qos_queue_by_external_ids(const struct smap *external_ids, + struct ovsdb_idl_index *ovsrec_queue_by_external_ids) +@@ -1144,33 +1193,22 @@ local_binding_set_pb(struct shash *local_bindings, const char *pb_name, + * - set the 'pb.up' field to true if 'parent_pb.up' is 'true' (e.g., for + * container and virtual ports). + * +- * Returns false if lport is not claimed due to 'sb_readonly'. +- * Returns true otherwise. +- * + * Note: + * Updates the 'pb->up' field only if it's explicitly set to 'false'. + * This is to ensure compatibility with older versions of ovn-northd. + */ +-static bool ++void + claimed_lport_set_up(const struct sbrec_port_binding *pb, +- const struct sbrec_port_binding *parent_pb, +- bool sb_readonly) ++ const struct sbrec_port_binding *parent_pb) + { +- /* When notify_up is false in claim_port(), no state is created +- * by if_status_mgr. In such cases, return false (i.e. trigger recompute) +- * if we can't update sb (because it is readonly). +- */ + bool up = true; + if (!parent_pb || (parent_pb->n_up && parent_pb->up[0])) { +- if (!sb_readonly) { +- if (pb->n_up) { +- sbrec_port_binding_set_up(pb, &up, 1); +- } +- } else if (pb->n_up && !pb->up[0]) { +- return false; ++ if (pb->n_up) { ++ VLOG_INFO("Setting lport %s up in Southbound", ++ pb->logical_port); ++ sbrec_port_binding_set_up(pb, &up, 1); + } + } +- return true; + } + + typedef void (*set_func)(const struct sbrec_port_binding *pb, +@@ -1250,7 +1288,7 @@ remove_additional_chassis(const struct sbrec_port_binding *pb, + remove_additional_encap_for_chassis(pb, chassis_rec); + } + +-static bool ++bool + lport_maybe_postpone(const char *port_name, long long int now, + struct sset *postponed_ports) + { +@@ -1273,7 +1311,7 @@ claim_lport(const struct sbrec_port_binding *pb, + const struct sbrec_port_binding *parent_pb, + const struct sbrec_chassis *chassis_rec, + const struct ovsrec_interface *iface_rec, +- bool sb_readonly, bool notify_up, ++ bool sb_readonly, bool is_vif, + struct hmap *tracked_datapaths, + struct if_status_mgr *if_mgr, + struct sset *postponed_ports) +@@ -1298,39 +1336,27 @@ claim_lport(const struct sbrec_port_binding *pb, + } + update_tracked = true; + +- if (!notify_up) { +- if (!claimed_lport_set_up(pb, parent_pb, sb_readonly)) { +- return false; +- } +- if (sb_readonly) { +- return false; +- } +- set_pb_chassis_in_sbrec(pb, chassis_rec, true); +- } else { +- if_status_mgr_claim_iface(if_mgr, pb, chassis_rec, iface_rec, +- sb_readonly, can_bind); +- } ++ if_status_mgr_claim_iface(if_mgr, pb, chassis_rec, iface_rec, ++ sb_readonly, can_bind, is_vif, ++ parent_pb); + register_claim_timestamp(pb->logical_port, now); + sset_find_and_delete(postponed_ports, pb->logical_port); + } else { +- if (!notify_up) { +- if (!claimed_lport_set_up(pb, parent_pb, sb_readonly)) { +- return false; +- } +- } else { +- if ((pb->n_up && !pb->up[0]) || +- !smap_get_bool(&iface_rec->external_ids, +- OVN_INSTALLED_EXT_ID, false)) { +- if_status_mgr_claim_iface(if_mgr, pb, chassis_rec, +- iface_rec, sb_readonly, +- can_bind); +- } ++ update_tracked = true; ++ if ((pb->n_up && !pb->up[0]) || ++ (is_vif && !smap_get_bool(&iface_rec->external_ids, ++ OVN_INSTALLED_EXT_ID, false))) { ++ if_status_mgr_claim_iface(if_mgr, pb, chassis_rec, ++ iface_rec, sb_readonly, ++ can_bind, is_vif, ++ parent_pb); + } + } + } else if (can_bind == CAN_BIND_AS_ADDITIONAL) { + if (!is_additional_chassis(pb, chassis_rec)) { + if_status_mgr_claim_iface(if_mgr, pb, chassis_rec, iface_rec, +- sb_readonly, can_bind); ++ sb_readonly, can_bind, is_vif, ++ parent_pb); + update_tracked = true; + } + } +@@ -1524,8 +1550,8 @@ consider_vif_lport_(const struct sbrec_port_binding *pb, + tracked_datapath_lport_add(pb, TRACKED_RESOURCE_UPDATED, + b_ctx_out->tracked_dp_bindings); + } +- if (b_lport->lbinding->iface && b_ctx_in->ovs_idl_txn) { +- configure_qos(pb, b_ctx_in, b_ctx_out); ++ if (b_lport->lbinding->iface) { ++ add_or_del_qos_port(pb->logical_port, true); + } + } else { + /* We could, but can't claim the lport. */ +@@ -1814,8 +1840,10 @@ consider_nonvif_lport_(const struct sbrec_port_binding *pb, + b_ctx_out->postponed_ports); + } + +- if (pb->chassis == b_ctx_in->chassis_rec || +- is_additional_chassis(pb, b_ctx_in->chassis_rec)) { ++ if (pb->chassis == b_ctx_in->chassis_rec ++ || is_additional_chassis(pb, b_ctx_in->chassis_rec) ++ || if_status_is_port_claimed(b_ctx_out->if_mgr, ++ pb->logical_port)) { + return release_lport(pb, b_ctx_in->chassis_rec, + !b_ctx_in->ovnsb_idl_txn, + b_ctx_out->tracked_dp_bindings, +@@ -1851,7 +1879,6 @@ consider_l3gw_lport(const struct sbrec_port_binding *pb, + + static void + consider_localnet_lport(const struct sbrec_port_binding *pb, +- struct binding_ctx_in *b_ctx_in, + struct binding_ctx_out *b_ctx_out) + { + struct local_datapath *ld +@@ -1860,14 +1887,23 @@ consider_localnet_lport(const struct sbrec_port_binding *pb, + if (!ld) { + return; + } ++ ++ bool pb_localnet_learn_fdb = smap_get_bool(&pb->options, ++ "localnet_learn_fdb", false); ++ if (pb_localnet_learn_fdb != b_ctx_out->localnet_learn_fdb) { ++ b_ctx_out->localnet_learn_fdb = pb_localnet_learn_fdb; ++ if (b_ctx_out->tracked_dp_bindings) { ++ b_ctx_out->localnet_learn_fdb_changed = true; ++ tracked_datapath_lport_add(pb, TRACKED_RESOURCE_UPDATED, ++ b_ctx_out->tracked_dp_bindings); ++ } ++ } ++ + /* Add all localnet ports to local_ifaces so that we allocate ct zones + * for them. */ + update_local_lports(pb->logical_port, b_ctx_out); + +- if (b_ctx_in->ovs_idl_txn) { +- configure_qos(pb, b_ctx_in, b_ctx_out); +- } +- ++ add_or_del_qos_port(pb->logical_port, true); + update_related_lport(pb, b_ctx_out); + } + +@@ -1988,6 +2024,7 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) + return; + } + ++ shash_clear_free_data(&_qos_ports); + struct shash bridge_mappings = SHASH_INITIALIZER(&bridge_mappings); + + if (b_ctx_in->br_int) { +@@ -2103,7 +2140,7 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) + * accordingly. */ + struct lport *lnet_lport; + LIST_FOR_EACH_POP (lnet_lport, list_node, &localnet_lports) { +- consider_localnet_lport(lnet_lport->pb, b_ctx_in, b_ctx_out); ++ consider_localnet_lport(lnet_lport->pb, b_ctx_out); + update_ld_localnet_port(lnet_lport->pb, &bridge_mappings, + b_ctx_out->local_datapaths); + free(lnet_lport); +@@ -2345,7 +2382,7 @@ consider_iface_release(const struct ovsrec_interface *iface_rec, + + lbinding = local_binding_find(local_bindings, iface_id); + +- if (lbinding) { ++ if (lbinding) { + if (lbinding->multiple_bindings) { + VLOG_INFO("Multiple bindings for %s: force recompute to clean up", + iface_id); +@@ -2365,54 +2402,50 @@ consider_iface_release(const struct ovsrec_interface *iface_rec, + return true; + } + } +- } + +- struct binding_lport *b_lport = +- local_binding_get_primary_or_localport_lport(lbinding); +- if (is_binding_lport_this_chassis(b_lport, b_ctx_in->chassis_rec)) { +- struct local_datapath *ld = +- get_local_datapath(b_ctx_out->local_datapaths, +- b_lport->pb->datapath->tunnel_key); +- if (ld) { +- remove_pb_from_local_datapath(b_lport->pb, +- b_ctx_out, ld); +- } +- +- remove_stale_qos_entry(b_ctx_in->ovs_idl_txn, b_lport->pb, +- b_ctx_in->ovsrec_port_by_qos, +- b_ctx_in->qos_table, b_ctx_out->qos_map); ++ struct binding_lport *b_lport = ++ local_binding_get_primary_or_localport_lport(lbinding); + +- /* Release the primary binding lport and other children lports if +- * any. */ +- LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { +- if (!release_binding_lport(b_ctx_in->chassis_rec, b_lport, +- !b_ctx_in->ovnsb_idl_txn, +- b_ctx_out)) { +- return false; ++ if (is_binding_lport_this_chassis(b_lport, b_ctx_in->chassis_rec)) { ++ struct local_datapath *ld = ++ get_local_datapath(b_ctx_out->local_datapaths, ++ b_lport->pb->datapath->tunnel_key); ++ if (ld) { ++ remove_pb_from_local_datapath(b_lport->pb, ++ b_ctx_out, ld); + } +- } +- if (lbinding->iface && lbinding->iface->name) { +- if_status_mgr_remove_ovn_installed(b_ctx_out->if_mgr, +- lbinding->iface->name, +- &lbinding->iface->header_.uuid); +- } ++ add_or_del_qos_port(b_lport->pb->logical_port, false); + +- } else if (lbinding && b_lport && b_lport->type == LP_LOCALPORT) { +- /* lbinding is associated with a localport. Remove it from the +- * related lports. */ +- remove_related_lport(b_lport->pb, b_ctx_out); +- } ++ /* Release the primary binding lport and other children lports if ++ * any. */ ++ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { ++ if (!release_binding_lport(b_ctx_in->chassis_rec, b_lport, ++ !b_ctx_in->ovnsb_idl_txn, ++ b_ctx_out)) { ++ return false; ++ } ++ } ++ if (lbinding->iface && lbinding->iface->name) { ++ if_status_mgr_remove_ovn_installed(b_ctx_out->if_mgr, ++ lbinding->iface->name, ++ &lbinding->iface->header_.uuid); ++ } + +- if (lbinding) { +- /* Clear the iface of the local binding. */ +- lbinding->iface = NULL; +- } ++ } else if (b_lport && b_lport->type == LP_LOCALPORT) { ++ /* lbinding is associated with a localport. Remove it from the ++ * related lports. */ ++ remove_related_lport(b_lport->pb, b_ctx_out); ++ } + +- /* Check if the lbinding has children of type PB_CONTAINER. +- * If so, don't delete the local_binding. */ +- if (lbinding && !is_lbinding_container_parent(lbinding)) { +- local_binding_delete(lbinding, local_bindings, binding_lports, +- b_ctx_out->if_mgr); ++ /* Check if the lbinding has children of type PB_CONTAINER. ++ * If so, don't delete the local_binding. */ ++ if (!is_lbinding_container_parent(lbinding)) { ++ local_binding_delete(lbinding, local_bindings, binding_lports, ++ b_ctx_out->if_mgr); ++ } else { ++ /* Clear the iface of the local binding. */ ++ lbinding->iface = NULL; ++ } + } + + remove_local_lports(iface_id, b_ctx_out); +@@ -2938,7 +2971,7 @@ handle_updated_port(struct binding_ctx_in *b_ctx_in, + break; + + case LP_LOCALNET: { +- consider_localnet_lport(pb, b_ctx_in, b_ctx_out); ++ consider_localnet_lport(pb, b_ctx_out); + + struct shash bridge_mappings = + SHASH_INITIALIZER(&bridge_mappings); +@@ -3026,9 +3059,7 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, + shash_add(&deleted_other_pbs, pb->logical_port, pb); + } + +- remove_stale_qos_entry(b_ctx_in->ovs_idl_txn, pb, +- b_ctx_in->ovsrec_port_by_qos, +- b_ctx_in->qos_table, b_ctx_out->qos_map); ++ add_or_del_qos_port(pb->logical_port, false); + } + + struct shash_node *node; +@@ -3140,7 +3171,7 @@ delete_done: + b_ctx_in->sbrec_port_binding_by_datapath) { + enum en_lport_type lport_type = get_lport_type(pb); + if (lport_type == LP_LOCALNET) { +- consider_localnet_lport(pb, b_ctx_in, b_ctx_out); ++ consider_localnet_lport(pb, b_ctx_out); + update_ld_localnet_port(pb, &bridge_mappings, + b_ctx_out->local_datapaths); + } else if (lport_type == LP_EXTERNAL) { +@@ -3207,7 +3238,7 @@ local_binding_delete(struct local_binding *lbinding, + struct if_status_mgr *if_mgr) + { + shash_find_and_delete(local_bindings, lbinding->name); +- if_status_mgr_delete_iface(if_mgr, lbinding->name); ++ if_status_mgr_delete_iface(if_mgr, lbinding->name, lbinding->iface); + local_binding_destroy(lbinding, binding_lports); + } + +@@ -3424,6 +3455,80 @@ port_binding_set_down(const struct sbrec_chassis *chassis_rec, + } + } + ++void ++port_binding_set_up(const struct sbrec_chassis *chassis_rec, ++ const struct sbrec_port_binding_table *pb_table, ++ const char *iface_id, ++ const struct uuid *pb_uuid) ++{ ++ const struct sbrec_port_binding *pb = ++ sbrec_port_binding_table_get_for_uuid(pb_table, pb_uuid); ++ if (!pb) { ++ VLOG_DBG("port_binding already deleted for %s", iface_id); ++ return; ++ } ++ if (pb->n_up && !pb->up[0]) { ++ bool up = true; ++ sbrec_port_binding_set_up(pb, &up, 1); ++ VLOG_INFO("Setting lport %s up in Southbound", pb->logical_port); ++ set_pb_chassis_in_sbrec(pb, chassis_rec, true); ++ } ++} ++ ++void ++port_binding_set_pb(const struct sbrec_chassis *chassis_rec, ++ const struct sbrec_port_binding_table *pb_table, ++ const char *iface_id, ++ const struct uuid *pb_uuid, ++ enum can_bind bind_type) ++{ ++ const struct sbrec_port_binding *pb = ++ sbrec_port_binding_table_get_for_uuid(pb_table, pb_uuid); ++ if (!pb) { ++ VLOG_DBG("port_binding already deleted for %s", iface_id); ++ return; ++ } ++ if (bind_type == CAN_BIND_AS_MAIN) { ++ set_pb_chassis_in_sbrec(pb, chassis_rec, true); ++ } else if (bind_type == CAN_BIND_AS_ADDITIONAL) { ++ set_pb_additional_chassis_in_sbrec(pb, chassis_rec, true); ++ } ++} ++ ++bool ++port_binding_pb_chassis_is_set( ++ const struct sbrec_chassis *chassis_rec, ++ const struct sbrec_port_binding_table *pb_table, ++ const struct uuid *pb_uuid) ++{ ++ const struct sbrec_port_binding *pb = ++ sbrec_port_binding_table_get_for_uuid(pb_table, pb_uuid); ++ ++ if (pb && ((pb->chassis == chassis_rec) || ++ is_additional_chassis(pb, chassis_rec))) { ++ return true; ++ } ++ return false; ++} ++ ++bool ++port_binding_is_up(const struct sbrec_chassis *chassis_rec, ++ const struct sbrec_port_binding_table *pb_table, ++ const struct uuid *pb_uuid) ++{ ++ const struct sbrec_port_binding *pb = ++ sbrec_port_binding_table_get_for_uuid(pb_table, pb_uuid); ++ ++ if (!pb || pb->chassis != chassis_rec) { ++ return false; ++ } ++ ++ if (pb->n_up && !pb->up[0]) { ++ return false; ++ } ++ return true; ++} ++ + static void + binding_lport_set_up(struct binding_lport *b_lport, bool sb_readonly) + { +@@ -3614,5 +3719,6 @@ void + binding_destroy(void) + { + shash_destroy_free_data(&_claimed_ports); ++ shash_destroy_free_data(&_qos_ports); + sset_clear(&_postponed_ports); + } +diff --git a/controller/binding.h b/controller/binding.h +index 24bc84079..75e7a2679 100644 +--- a/controller/binding.h ++++ b/controller/binding.h +@@ -108,6 +108,9 @@ struct binding_ctx_out { + struct if_status_mgr *if_mgr; + + struct sset *postponed_ports; ++ ++ bool localnet_learn_fdb; ++ bool localnet_learn_fdb_changed; + }; + + /* Local bindings. binding.c module binds the logical port (represented by +@@ -217,6 +220,18 @@ void port_binding_set_down(const struct sbrec_chassis *chassis_rec, + const struct sbrec_port_binding_table *pb_table, + const char *iface_id, + const struct uuid *pb_uuid); ++void port_binding_set_up(const struct sbrec_chassis *chassis_rec, ++ const struct sbrec_port_binding_table *, ++ const char *iface_id, ++ const struct uuid *pb_uuid); ++void port_binding_set_pb(const struct sbrec_chassis *chassis_rec, ++ const struct sbrec_port_binding_table *, ++ const char *iface_id, ++ const struct uuid *pb_uuid, ++ enum can_bind bind_type); ++bool port_binding_pb_chassis_is_set(const struct sbrec_chassis *chassis_rec, ++ const struct sbrec_port_binding_table *, ++ const struct uuid *pb_uuid); + + /* This structure represents a logical port (or port binding) + * which is associated with 'struct local_binding'. +@@ -250,4 +265,20 @@ void binding_destroy(void); + + void destroy_qos_map(struct hmap *); + ++void update_qos(struct ovsdb_idl_index * sbrec_port_binding_by_name, ++ struct ovsdb_idl_txn *ovs_idl_txn, ++ struct ovsdb_idl_index *ovsrec_port_by_qos, ++ const struct ovsrec_qos_table *qos_table, ++ struct hmap *qos_map, ++ const struct ovsrec_open_vswitch_table *ovs_table, ++ const struct ovsrec_bridge_table *bridge_table); ++ ++bool lport_maybe_postpone(const char *port_name, long long int now, ++ struct sset *postponed_ports); ++ ++void claimed_lport_set_up(const struct sbrec_port_binding *pb, ++ const struct sbrec_port_binding *parent_pb); ++bool port_binding_is_up(const struct sbrec_chassis *chassis_rec, ++ const struct sbrec_port_binding_table *, ++ const struct uuid *pb_uuid); + #endif /* controller/binding.h */ +diff --git a/controller/chassis.c b/controller/chassis.c +index 031bd4463..a6f13ccc4 100644 +--- a/controller/chassis.c ++++ b/controller/chassis.c +@@ -369,6 +369,7 @@ chassis_build_other_config(const struct ovs_chassis_cfg *ovs_cfg, + smap_replace(config, OVN_FEATURE_MAC_BINDING_TIMESTAMP, "true"); + smap_replace(config, OVN_FEATURE_CT_LB_RELATED, "true"); + smap_replace(config, OVN_FEATURE_FDB_TIMESTAMP, "true"); ++ smap_replace(config, OVN_FEATURE_LS_DPG_COLUMN, "true"); + } + + /* +@@ -502,6 +503,12 @@ chassis_other_config_changed(const struct ovs_chassis_cfg *ovs_cfg, + return true; + } + ++ if (!smap_get_bool(&chassis_rec->other_config, ++ OVN_FEATURE_LS_DPG_COLUMN, ++ false)) { ++ return true; ++ } ++ + return false; + } + +@@ -632,6 +639,7 @@ update_supported_sset(struct sset *supported) + sset_add(supported, OVN_FEATURE_MAC_BINDING_TIMESTAMP); + sset_add(supported, OVN_FEATURE_CT_LB_RELATED); + sset_add(supported, OVN_FEATURE_FDB_TIMESTAMP); ++ sset_add(supported, OVN_FEATURE_LS_DPG_COLUMN); + } + + static void +diff --git a/controller/if-status.c b/controller/if-status.c +index 6c5afc866..6e8aa1f7e 100644 +--- a/controller/if-status.c ++++ b/controller/if-status.c +@@ -177,7 +177,9 @@ static const char *if_state_names[] = { + + struct ovs_iface { + char *id; /* Extracted from OVS external_ids.iface_id. */ ++ char *name; /* OVS iface name. */ + struct uuid pb_uuid; /* Port_binding uuid */ ++ struct uuid parent_pb_uuid; /* Parent port_binding uuid */ + enum if_state state; /* State of the interface in the state machine. */ + uint32_t install_seqno; /* Seqno at which this interface is expected to + * be fully programmed in OVS. Only used in state +@@ -185,6 +187,7 @@ struct ovs_iface { + */ + uint16_t mtu; /* Extracted from OVS interface.mtu field. */ + enum can_bind bind_type;/* CAN_BIND_AS_MAIN or CAN_BIND_AS_ADDITIONAL */ ++ bool is_vif; /* Vifs, container or virtual ports */ + }; + + static uint64_t ifaces_usage; +@@ -224,6 +227,7 @@ static void if_status_mgr_update_bindings( + struct if_status_mgr *mgr, struct local_binding_data *binding_data, + const struct sbrec_chassis *, + const struct ovsrec_interface_table *iface_table, ++ const struct sbrec_port_binding_table *pb_table, + bool sb_readonly, bool ovs_readonly); + + static void ovn_uninstall_hash_account_mem(const char *name, bool erase); +@@ -273,12 +277,23 @@ if_status_mgr_destroy(struct if_status_mgr *mgr) + free(mgr); + } + ++/* is_vif controls whether we wait for flows to be updated ++ * before setting the interface up. It is true for VIF, CONTAINER ++ * and VIRTUAL ports. ++ * Non-VIF ports are reported up as soon as they are claimed ++ * to maintain compatibility with older versions. ++ * See aae25e6 binding: Correctly set Port_Binding.up for container/virtual ++ * ports. ++ */ ++ + void + if_status_mgr_claim_iface(struct if_status_mgr *mgr, + const struct sbrec_port_binding *pb, + const struct sbrec_chassis *chassis_rec, + const struct ovsrec_interface *iface_rec, +- bool sb_readonly, enum can_bind bind_type) ++ bool sb_readonly, enum can_bind bind_type, ++ bool is_vif, ++ const struct sbrec_port_binding *parent_pb) + { + const char *iface_id = pb->logical_port; + struct ovs_iface *iface = shash_find_data(&mgr->ifaces, iface_id); +@@ -287,8 +302,13 @@ if_status_mgr_claim_iface(struct if_status_mgr *mgr, + iface = ovs_iface_create(mgr, iface_id, iface_rec, OIF_CLAIMED); + } + iface->bind_type = bind_type; ++ iface->is_vif = is_vif; + + memcpy(&iface->pb_uuid, &pb->header_.uuid, sizeof(iface->pb_uuid)); ++ if (parent_pb) { ++ memcpy(&iface->parent_pb_uuid, &parent_pb->header_.uuid, ++ sizeof(iface->pb_uuid)); ++ } + if (!sb_readonly) { + if (bind_type == CAN_BIND_AS_MAIN) { + set_pb_chassis_in_sbrec(pb, chassis_rec, true); +@@ -357,7 +377,8 @@ if_status_mgr_release_iface(struct if_status_mgr *mgr, const char *iface_id) + } + + void +-if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id) ++if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id, ++ const struct ovsrec_interface *iface_rec) + { + struct ovs_iface *iface = shash_find_data(&mgr->ifaces, iface_id); + +@@ -365,6 +386,12 @@ if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id) + return; + } + ++ if (iface_rec && strcmp(iface->name, iface_rec->name)) { ++ VLOG_DBG("Interface %s not deleted as port %s bound to %s", ++ iface_rec->name, iface_id, iface->name); ++ return; ++ } ++ + switch (iface->state) { + case OIF_CLAIMED: + case OIF_INSTALL_FLOWS: +@@ -394,6 +421,7 @@ if_status_handle_claims(struct if_status_mgr *mgr, + struct local_binding_data *binding_data, + const struct sbrec_chassis *chassis_rec, + struct hmap *tracked_datapath, ++ const struct sbrec_port_binding_table *pb_table, + bool sb_readonly) + { + if (!binding_data || sb_readonly) { +@@ -406,9 +434,15 @@ if_status_handle_claims(struct if_status_mgr *mgr, + bool rc = false; + HMAPX_FOR_EACH (node, &mgr->ifaces_per_state[OIF_CLAIMED]) { + struct ovs_iface *iface = node->data; +- VLOG_INFO("if_status_handle_claims for %s", iface->id); +- local_binding_set_pb(bindings, iface->id, chassis_rec, +- tracked_datapath, true, iface->bind_type); ++ VLOG_DBG("if_status_handle_claims for %s, is_vif = %d", iface->id, ++ iface->is_vif); ++ if (iface->is_vif) { ++ local_binding_set_pb(bindings, iface->id, chassis_rec, ++ tracked_datapath, true, iface->bind_type); ++ } else { ++ port_binding_set_pb(chassis_rec, pb_table, iface->id, ++ &iface->pb_uuid, iface->bind_type); ++ } + rc = true; + } + return rc; +@@ -470,18 +504,37 @@ if_status_mgr_update(struct if_status_mgr *mgr, + */ + HMAPX_FOR_EACH_SAFE (node, &mgr->ifaces_per_state[OIF_MARK_UP]) { + struct ovs_iface *iface = node->data; +- +- if (!local_bindings_pb_chassis_is_set(bindings, iface->id, +- chassis_rec)) { +- if (!sb_readonly) { +- local_binding_set_pb(bindings, iface->id, chassis_rec, +- NULL, true, iface->bind_type); +- } else { +- continue; ++ if (iface->is_vif) { ++ if (!local_bindings_pb_chassis_is_set(bindings, iface->id, ++ chassis_rec)) { ++ if (!sb_readonly) { ++ long long int now = time_msec(); ++ if (lport_maybe_postpone(iface->id, now, ++ get_postponed_ports())) { ++ continue; ++ } ++ local_binding_set_pb(bindings, iface->id, chassis_rec, ++ NULL, true, iface->bind_type); ++ } else { ++ continue; ++ } ++ } ++ if (local_binding_is_up(bindings, iface->id, chassis_rec)) { ++ ovs_iface_set_state(mgr, iface, OIF_INSTALLED); ++ } ++ } else { ++ if (!port_binding_pb_chassis_is_set(chassis_rec, pb_table, ++ &iface->pb_uuid)) { ++ if (!sb_readonly) { ++ port_binding_set_pb(chassis_rec, pb_table, iface->id, ++ &iface->pb_uuid, iface->bind_type); ++ } else { ++ continue; ++ } ++ } ++ if (port_binding_is_up(chassis_rec, pb_table, &iface->pb_uuid)) { ++ ovs_iface_set_state(mgr, iface, OIF_INSTALLED); + } +- } +- if (local_binding_is_up(bindings, iface->id, chassis_rec)) { +- ovs_iface_set_state(mgr, iface, OIF_INSTALLED); + } + } + +@@ -510,9 +563,13 @@ if_status_mgr_update(struct if_status_mgr *mgr, + if (!sb_readonly) { + HMAPX_FOR_EACH_SAFE (node, &mgr->ifaces_per_state[OIF_INSTALL_FLOWS]) { + struct ovs_iface *iface = node->data; +- + if (!local_bindings_pb_chassis_is_set(bindings, iface->id, + chassis_rec)) { ++ long long int now = time_msec(); ++ if (lport_maybe_postpone(iface->id, now, ++ get_postponed_ports())) { ++ continue; ++ } + local_binding_set_pb(bindings, iface->id, chassis_rec, + NULL, true, iface->bind_type); + } +@@ -528,9 +585,13 @@ if_status_mgr_update(struct if_status_mgr *mgr, + /* No need to to update pb->chassis as already done + * in if_status_handle_claims or if_status_mgr_claim_iface + */ +- ovs_iface_set_state(mgr, iface, OIF_INSTALL_FLOWS); +- iface->install_seqno = mgr->iface_seqno + 1; +- new_ifaces = true; ++ if (iface->is_vif) { ++ ovs_iface_set_state(mgr, iface, OIF_INSTALL_FLOWS); ++ iface->install_seqno = mgr->iface_seqno + 1; ++ new_ifaces = true; ++ } else { ++ ovs_iface_set_state(mgr, iface, OIF_MARK_UP); ++ } + } + } else { + HMAPX_FOR_EACH_SAFE (node, &mgr->ifaces_per_state[OIF_CLAIMED]) { +@@ -587,6 +648,7 @@ if_status_mgr_run(struct if_status_mgr *mgr, + struct local_binding_data *binding_data, + const struct sbrec_chassis *chassis_rec, + const struct ovsrec_interface_table *iface_table, ++ const struct sbrec_port_binding_table *pb_table, + bool sb_readonly, bool ovs_readonly) + { + struct ofctrl_acked_seqnos *acked_seqnos = +@@ -622,15 +684,18 @@ if_status_mgr_run(struct if_status_mgr *mgr, + + /* Update binding states. */ + if_status_mgr_update_bindings(mgr, binding_data, chassis_rec, +- iface_table, ++ iface_table, pb_table, + sb_readonly, ovs_readonly); + } + + static void +-ovs_iface_account_mem(const char *iface_id, bool erase) ++ovs_iface_account_mem(const char *iface_id, char *iface_name, bool erase) + { + uint32_t size = (strlen(iface_id) + sizeof(struct ovs_iface) + + sizeof(struct shash_node)); ++ if (iface_name) { ++ size += strlen(iface_name); ++ } + if (erase) { + ifaces_usage -= size; + } else { +@@ -682,12 +747,18 @@ ovs_iface_create(struct if_status_mgr *mgr, const char *iface_id, + { + struct ovs_iface *iface = xzalloc(sizeof *iface); + +- VLOG_DBG("Interface %s create.", iface_id); ++ VLOG_DBG("Interface %s create for iface %s.", iface_id, ++ iface_rec ? iface_rec->name : ""); + iface->id = xstrdup(iface_id); + shash_add_nocopy(&mgr->ifaces, iface->id, iface); + ovs_iface_set_state(mgr, iface, state); +- ovs_iface_account_mem(iface_id, false); +- if_status_mgr_iface_update(mgr, iface_rec); ++ if (iface_rec) { ++ ovs_iface_account_mem(iface_id, iface_rec->name, false); ++ if_status_mgr_iface_update(mgr, iface_rec); ++ iface->name = xstrdup(iface_rec->name); ++ } else { ++ ovs_iface_account_mem(iface_id, NULL, false); ++ } + return iface; + } + +@@ -711,7 +782,8 @@ ovs_iface_destroy(struct if_status_mgr *mgr, struct ovs_iface *iface) + if (node) { + shash_steal(&mgr->ifaces, node); + } +- ovs_iface_account_mem(iface->id, true); ++ ovs_iface_account_mem(iface->id, iface->name, true); ++ free(iface->name); + free(iface->id); + free(iface); + } +@@ -752,6 +824,7 @@ if_status_mgr_update_bindings(struct if_status_mgr *mgr, + struct local_binding_data *binding_data, + const struct sbrec_chassis *chassis_rec, + const struct ovsrec_interface_table *iface_table, ++ const struct sbrec_port_binding_table *pb_table, + bool sb_readonly, bool ovs_readonly) + { + if (!binding_data) { +@@ -788,9 +861,20 @@ if_status_mgr_update_bindings(struct if_status_mgr *mgr, + char *ts_now_str = xasprintf("%lld", time_wall_msec()); + HMAPX_FOR_EACH (node, &mgr->ifaces_per_state[OIF_MARK_UP]) { + struct ovs_iface *iface = node->data; +- +- local_binding_set_up(bindings, iface->id, chassis_rec, ts_now_str, +- sb_readonly, ovs_readonly); ++ if (iface->is_vif) { ++ local_binding_set_up(bindings, iface->id, chassis_rec, ts_now_str, ++ sb_readonly, ovs_readonly); ++ } else if (!sb_readonly) { ++ const struct sbrec_port_binding *pb = ++ sbrec_port_binding_table_get_for_uuid(pb_table, ++ &iface->pb_uuid); ++ if (pb) { ++ const struct sbrec_port_binding *parent_pb = ++ sbrec_port_binding_table_get_for_uuid(pb_table, ++ &iface->parent_pb_uuid); ++ claimed_lport_set_up(pb, parent_pb); ++ } ++ } + } + free(ts_now_str); + +diff --git a/controller/if-status.h b/controller/if-status.h +index 9714f6d8d..4ae5ad481 100644 +--- a/controller/if-status.h ++++ b/controller/if-status.h +@@ -32,9 +32,12 @@ void if_status_mgr_claim_iface(struct if_status_mgr *, + const struct sbrec_port_binding *pb, + const struct sbrec_chassis *chassis_rec, + const struct ovsrec_interface *iface_rec, +- bool sb_readonly, enum can_bind bind_type); ++ bool sb_readonly, enum can_bind bind_type, ++ bool notify_up, ++ const struct sbrec_port_binding *parent_pb); + void if_status_mgr_release_iface(struct if_status_mgr *, const char *iface_id); +-void if_status_mgr_delete_iface(struct if_status_mgr *, const char *iface_id); ++void if_status_mgr_delete_iface(struct if_status_mgr *, const char *iface_id, ++ const struct ovsrec_interface *iface_rec); + + void if_status_mgr_update(struct if_status_mgr *, struct local_binding_data *, + const struct sbrec_chassis *chassis, +@@ -45,6 +48,7 @@ void if_status_mgr_update(struct if_status_mgr *, struct local_binding_data *, + void if_status_mgr_run(struct if_status_mgr *mgr, struct local_binding_data *, + const struct sbrec_chassis *, + const struct ovsrec_interface_table *iface_table, ++ const struct sbrec_port_binding_table *pb_table, + bool sb_readonly, bool ovs_readonly); + void if_status_mgr_get_memory_usage(struct if_status_mgr *mgr, + struct simap *usage); +@@ -54,6 +58,7 @@ bool if_status_handle_claims(struct if_status_mgr *mgr, + struct local_binding_data *binding_data, + const struct sbrec_chassis *chassis_rec, + struct hmap *tracked_datapath, ++ const struct sbrec_port_binding_table *pb_table, + bool sb_readonly); + void if_status_mgr_remove_ovn_installed(struct if_status_mgr *mgr, + const char *name, +diff --git a/controller/lflow.c b/controller/lflow.c +index f70080e8e..b0cf4253c 100644 +--- a/controller/lflow.c ++++ b/controller/lflow.c +@@ -1017,6 +1017,7 @@ convert_match_to_expr(const struct sbrec_logical_flow *lflow, + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "error parsing match \"%s\": %s", + lflow->match, error); ++ expr_destroy(e); + free(error); + return NULL; + } +@@ -1892,6 +1893,7 @@ add_lb_ct_snat_hairpin_vip_flow(const struct ovn_controller_lb *lb, + local_datapaths, &match, + &ofpacts, flow_table); + } ++ /* datapath_group column is deprecated. */ + if (lb->slb->datapath_group) { + for (size_t i = 0; i < lb->slb->datapath_group->n_datapaths; i++) { + add_lb_ct_snat_hairpin_for_dp( +@@ -1900,6 +1902,15 @@ add_lb_ct_snat_hairpin_vip_flow(const struct ovn_controller_lb *lb, + local_datapaths, &match, &ofpacts, flow_table); + } + } ++ if (lb->slb->ls_datapath_group) { ++ for (size_t i = 0; ++ i < lb->slb->ls_datapath_group->n_datapaths; i++) { ++ add_lb_ct_snat_hairpin_for_dp( ++ lb, !!lb_vip->vip_port, ++ lb->slb->ls_datapath_group->datapaths[i], ++ local_datapaths, &match, &ofpacts, flow_table); ++ } ++ } + } + + ofpbuf_uninit(&ofpacts); +@@ -2055,11 +2066,17 @@ lflow_handle_changed_static_mac_bindings( + static void + consider_fdb_flows(const struct sbrec_fdb *fdb, + const struct hmap *local_datapaths, +- struct ovn_desired_flow_table *flow_table) ++ struct ovn_desired_flow_table *flow_table, ++ struct ovsdb_idl_index *sbrec_port_binding_by_key, ++ bool localnet_learn_fdb) + { +- if (!get_local_datapath(local_datapaths, fdb->dp_key)) { ++ struct local_datapath *ld = get_local_datapath(local_datapaths, ++ fdb->dp_key); ++ if (!ld) { + return; + } ++ const struct sbrec_port_binding *pb = lport_lookup_by_key_with_dp( ++ sbrec_port_binding_by_key, ld->datapath, fdb->port_key); + + struct eth_addr mac; + if (!eth_addr_from_string(fdb->mac, &mac)) { +@@ -2081,6 +2098,7 @@ consider_fdb_flows(const struct sbrec_fdb *fdb, + ofpbuf_clear(&ofpacts); + + uint8_t value = 1; ++ uint8_t is_vif = pb ? !strcmp(pb->type, "") : 0; + put_load(&value, sizeof value, MFF_LOG_FLAGS, + MLF_LOOKUP_FDB_BIT, 1, &ofpacts); + +@@ -2091,6 +2109,18 @@ consider_fdb_flows(const struct sbrec_fdb *fdb, + ofctrl_add_flow(flow_table, OFTABLE_LOOKUP_FDB, 100, + fdb->header_.uuid.parts[0], &lookup_match, &ofpacts, + &fdb->header_.uuid); ++ ++ if (is_vif && localnet_learn_fdb) { ++ struct match lookup_match_vif = MATCH_CATCHALL_INITIALIZER; ++ match_set_metadata(&lookup_match_vif, htonll(fdb->dp_key)); ++ match_set_dl_src(&lookup_match_vif, mac); ++ match_set_reg_masked(&lookup_match_vif, MFF_LOG_FLAGS - MFF_REG0, ++ MLF_LOCALNET, MLF_LOCALNET); ++ ++ ofctrl_add_flow(flow_table, OFTABLE_LOOKUP_FDB, 100, ++ fdb->header_.uuid.parts[0], &lookup_match_vif, ++ &ofpacts, &fdb->header_.uuid); ++ } + ofpbuf_uninit(&ofpacts); + } + +@@ -2099,11 +2129,14 @@ consider_fdb_flows(const struct sbrec_fdb *fdb, + static void + add_fdb_flows(const struct sbrec_fdb_table *fdb_table, + const struct hmap *local_datapaths, +- struct ovn_desired_flow_table *flow_table) ++ struct ovn_desired_flow_table *flow_table, ++ struct ovsdb_idl_index *sbrec_port_binding_by_key, ++ bool localnet_learn_fdb) + { + const struct sbrec_fdb *fdb; + SBREC_FDB_TABLE_FOR_EACH (fdb, fdb_table) { +- consider_fdb_flows(fdb, local_datapaths, flow_table); ++ consider_fdb_flows(fdb, local_datapaths, flow_table, ++ sbrec_port_binding_by_key, localnet_learn_fdb); + } + } + +@@ -2126,7 +2159,9 @@ lflow_run(struct lflow_ctx_in *l_ctx_in, struct lflow_ctx_out *l_ctx_out) + l_ctx_in->lb_hairpin_use_ct_mark, + l_ctx_out->flow_table); + add_fdb_flows(l_ctx_in->fdb_table, l_ctx_in->local_datapaths, +- l_ctx_out->flow_table); ++ l_ctx_out->flow_table, ++ l_ctx_in->sbrec_port_binding_by_key, ++ l_ctx_in->localnet_learn_fdb); + add_port_sec_flows(l_ctx_in->binding_lports, l_ctx_in->chassis, + l_ctx_out->flow_table); + } +@@ -2216,7 +2251,9 @@ lflow_add_flows_for_datapath(const struct sbrec_datapath_binding *dp, + SBREC_FDB_FOR_EACH_EQUAL (fdb_row, fdb_index_row, + l_ctx_in->sbrec_fdb_by_dp_key) { + consider_fdb_flows(fdb_row, l_ctx_in->local_datapaths, +- l_ctx_out->flow_table); ++ l_ctx_out->flow_table, ++ l_ctx_in->sbrec_port_binding_by_key, ++ l_ctx_in->localnet_learn_fdb); + } + sbrec_fdb_index_destroy_row(fdb_index_row); + +@@ -2280,6 +2317,15 @@ lflow_handle_flows_for_lport(const struct sbrec_port_binding *pb, + pb->logical_port)) { + consider_port_sec_flows(pb, l_ctx_out->flow_table); + } ++ if (l_ctx_in->localnet_learn_fdb_changed && l_ctx_in->localnet_learn_fdb) { ++ const struct sbrec_fdb *fdb; ++ SBREC_FDB_TABLE_FOR_EACH (fdb, l_ctx_in->fdb_table) { ++ consider_fdb_flows(fdb, l_ctx_in->local_datapaths, ++ l_ctx_out->flow_table, ++ l_ctx_in->sbrec_port_binding_by_key, ++ l_ctx_in->localnet_learn_fdb); ++ } ++ } + return true; + } + +@@ -2409,7 +2455,9 @@ lflow_handle_changed_fdbs(struct lflow_ctx_in *l_ctx_in, + VLOG_DBG("Add fdb flows for fdb "UUID_FMT, + UUID_ARGS(&fdb->header_.uuid)); + consider_fdb_flows(fdb, l_ctx_in->local_datapaths, +- l_ctx_out->flow_table); ++ l_ctx_out->flow_table, ++ l_ctx_in->sbrec_port_binding_by_key, ++ l_ctx_in->localnet_learn_fdb); + } + + return true; +diff --git a/controller/lflow.h b/controller/lflow.h +index 5da4385e4..9b7ffa19c 100644 +--- a/controller/lflow.h ++++ b/controller/lflow.h +@@ -100,6 +100,7 @@ struct lflow_ctx_in { + struct ovsdb_idl_index *sbrec_logical_flow_by_logical_datapath; + struct ovsdb_idl_index *sbrec_logical_flow_by_logical_dp_group; + struct ovsdb_idl_index *sbrec_port_binding_by_name; ++ struct ovsdb_idl_index *sbrec_port_binding_by_key; + struct ovsdb_idl_index *sbrec_fdb_by_dp_key; + struct ovsdb_idl_index *sbrec_mac_binding_by_datapath; + struct ovsdb_idl_index *sbrec_static_mac_binding_by_datapath; +@@ -127,6 +128,8 @@ struct lflow_ctx_in { + const struct flow_collector_ids *collector_ids; + const struct hmap *local_lbs; + bool lb_hairpin_use_ct_mark; ++ bool localnet_learn_fdb; ++ bool localnet_learn_fdb_changed; + }; + + struct lflow_ctx_out { +diff --git a/controller/local_data.c b/controller/local_data.c +index cf0b21bb1..ab18386c7 100644 +--- a/controller/local_data.c ++++ b/controller/local_data.c +@@ -56,6 +56,20 @@ static bool datapath_is_transit_switch(const struct sbrec_datapath_binding *); + + static uint64_t local_datapath_usage; + ++/* To be used when hmap_node.hash might be wrong e.g. tunnel_key got updated */ ++struct local_datapath * ++get_local_datapath_no_hash(const struct hmap *local_datapaths, ++ uint32_t tunnel_key) ++{ ++ struct local_datapath *ld; ++ HMAP_FOR_EACH (ld, hmap_node, local_datapaths) { ++ if (ld->datapath->tunnel_key == tunnel_key) { ++ return ld; ++ } ++ } ++ return NULL; ++} ++ + struct local_datapath * + get_local_datapath(const struct hmap *local_datapaths, uint32_t tunnel_key) + { +@@ -661,8 +675,24 @@ lb_is_local(const struct sbrec_load_balancer *sbrec_lb, + } + } + ++ /* datapath_group column is deprecated. */ + struct sbrec_logical_dp_group *dp_group = sbrec_lb->datapath_group; ++ for (size_t i = 0; dp_group && i < dp_group->n_datapaths; i++) { ++ if (get_local_datapath(local_datapaths, ++ dp_group->datapaths[i]->tunnel_key)) { ++ return true; ++ } ++ } ++ ++ dp_group = sbrec_lb->ls_datapath_group; ++ for (size_t i = 0; dp_group && i < dp_group->n_datapaths; i++) { ++ if (get_local_datapath(local_datapaths, ++ dp_group->datapaths[i]->tunnel_key)) { ++ return true; ++ } ++ } + ++ dp_group = sbrec_lb->lr_datapath_group; + for (size_t i = 0; dp_group && i < dp_group->n_datapaths; i++) { + if (get_local_datapath(local_datapaths, + dp_group->datapaths[i]->tunnel_key)) { +diff --git a/controller/local_data.h b/controller/local_data.h +index f6d8f725f..2a1a3c0f9 100644 +--- a/controller/local_data.h ++++ b/controller/local_data.h +@@ -69,6 +69,10 @@ struct local_datapath *local_datapath_alloc( + const struct sbrec_datapath_binding *); + struct local_datapath *get_local_datapath(const struct hmap *, + uint32_t tunnel_key); ++struct local_datapath *get_local_datapath_no_hash( ++ const struct hmap *local_datapaths, ++ uint32_t tunnel_key); ++ + bool + need_add_peer_to_local( + struct ovsdb_idl_index *sbrec_port_binding_by_name, +diff --git a/controller/lport.c b/controller/lport.c +index add7e91aa..b3721024b 100644 +--- a/controller/lport.c ++++ b/controller/lport.c +@@ -44,13 +44,10 @@ lport_lookup_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name, + } + + const struct sbrec_port_binding * +-lport_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key, +- struct ovsdb_idl_index *sbrec_port_binding_by_key, +- uint64_t dp_key, uint64_t port_key) ++lport_lookup_by_key_with_dp(struct ovsdb_idl_index *sbrec_port_binding_by_key, ++ const struct sbrec_datapath_binding *db, ++ uint64_t port_key) + { +- /* Lookup datapath corresponding to dp_key. */ +- const struct sbrec_datapath_binding *db = datapath_lookup_by_key( +- sbrec_datapath_binding_by_key, dp_key); + if (!db) { + return NULL; + } +@@ -69,6 +66,19 @@ lport_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key, + return retval; + } + ++const struct sbrec_port_binding * ++lport_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key, ++ struct ovsdb_idl_index *sbrec_port_binding_by_key, ++ uint64_t dp_key, uint64_t port_key) ++{ ++ /* Lookup datapath corresponding to dp_key. */ ++ const struct sbrec_datapath_binding *db = datapath_lookup_by_key( ++ sbrec_datapath_binding_by_key, dp_key); ++ ++ return lport_lookup_by_key_with_dp(sbrec_port_binding_by_key, db, ++ port_key); ++} ++ + bool + lport_is_chassis_resident(struct ovsdb_idl_index *sbrec_port_binding_by_name, + const struct sbrec_chassis *chassis, +diff --git a/controller/lport.h b/controller/lport.h +index 644c67255..2f72aef5e 100644 +--- a/controller/lport.h ++++ b/controller/lport.h +@@ -43,6 +43,10 @@ const struct sbrec_port_binding *lport_lookup_by_key( + struct ovsdb_idl_index *sbrec_port_binding_by_key, + uint64_t dp_key, uint64_t port_key); + ++const struct sbrec_port_binding *lport_lookup_by_key_with_dp( ++ struct ovsdb_idl_index *sbrec_port_binding_by_key, ++ const struct sbrec_datapath_binding *dp, uint64_t port_key); ++ + enum can_bind { + CANNOT_BIND = 0, + CAN_BIND_AS_MAIN, +diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml +index 7b4100592..0b9641045 100644 +--- a/controller/ovn-controller.8.xml ++++ b/controller/ovn-controller.8.xml +@@ -363,7 +363,10 @@ + The boolean flag indicates if ovn-controller when create + tunnel ports should set local_ip parameter. Can be + heplful to pin source outer IP for the tunnel when multiple interfaces +- are used on the host for overlay traffic. ++ are used on the host for overlay traffic. This is also useful when ++ running multiple ovn-controller instances on the same ++ chassis, in which case this setting will guarantee that their tunnel ++ ports have unique configuration and can exist in parallel. + +
external_ids:garp-max-timeout-sec
+
+@@ -398,9 +401,11 @@ + names on the same host using the same vswitchd instance. + This may be useful when running a hybrid setup with more than one CMS + managing ports on the host, or to use different datapath types on the +- same host. Note that this ability is highly experimental and has known +- limitations (for example, stateful ACLs are not supported). Use at your +- own risk. ++ same host. Make sure you also set ++ external_ids:ovn-set-local-ip when using such ++ configuration. Also note that this ability is highly experimental and ++ has known limitations (for example, stateful ACLs are not supported). ++ Use at your own risk. +

+ +

+diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c +index b3e4e0da8..6787dda80 100644 +--- a/controller/ovn-controller.c ++++ b/controller/ovn-controller.c +@@ -88,7 +88,6 @@ + + VLOG_DEFINE_THIS_MODULE(main); + +-static unixctl_cb_func ovn_controller_exit; + static unixctl_cb_func ct_zone_list; + static unixctl_cb_func extend_table_list; + static unixctl_cb_func inject_pkt; +@@ -211,8 +210,8 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl, + { + /* Monitor Port_Bindings rows for local interfaces and local datapaths. + * +- * Monitor Logical_Flow, MAC_Binding, Multicast_Group, and DNS tables for +- * local datapaths. ++ * Monitor Logical_Flow, MAC_Binding, FDB, Multicast_Group, and DNS tables ++ * for local datapaths. + * + * Monitor Controller_Event rows for local chassis. + * +@@ -230,6 +229,7 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl, + struct ovsdb_idl_condition lf = OVSDB_IDL_CONDITION_INIT(&lf); + struct ovsdb_idl_condition ldpg = OVSDB_IDL_CONDITION_INIT(&ldpg); + struct ovsdb_idl_condition mb = OVSDB_IDL_CONDITION_INIT(&mb); ++ struct ovsdb_idl_condition fdb = OVSDB_IDL_CONDITION_INIT(&fdb); + struct ovsdb_idl_condition mg = OVSDB_IDL_CONDITION_INIT(&mg); + struct ovsdb_idl_condition dns = OVSDB_IDL_CONDITION_INIT(&dns); + struct ovsdb_idl_condition ce = OVSDB_IDL_CONDITION_INIT(&ce); +@@ -248,6 +248,7 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl, + ovsdb_idl_condition_add_clause_true(&pb); + ovsdb_idl_condition_add_clause_true(&lf); + ovsdb_idl_condition_add_clause_true(&mb); ++ ovsdb_idl_condition_add_clause_true(&fdb); + ovsdb_idl_condition_add_clause_true(&mg); + ovsdb_idl_condition_add_clause_true(&dns); + ovsdb_idl_condition_add_clause_true(&ce); +@@ -337,6 +338,8 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl, + sbrec_logical_flow_add_clause_logical_datapath(&lf, OVSDB_F_EQ, + uuid); + sbrec_mac_binding_add_clause_datapath(&mb, OVSDB_F_EQ, uuid); ++ sbrec_fdb_add_clause_dp_key(&fdb, OVSDB_F_EQ, ++ ld->datapath->tunnel_key); + sbrec_multicast_group_add_clause_datapath(&mg, OVSDB_F_EQ, uuid); + sbrec_dns_add_clause_datapaths(&dns, OVSDB_F_INCLUDES, &uuid, 1); + sbrec_ip_multicast_add_clause_datapath(&ip_mcast, OVSDB_F_EQ, +@@ -360,6 +363,7 @@ out:; + sb_table_set_req_mon_condition(ovnsb_idl, logical_flow, &lf), + sb_table_set_req_mon_condition(ovnsb_idl, logical_dp_group, &ldpg), + sb_table_set_req_mon_condition(ovnsb_idl, mac_binding, &mb), ++ sb_table_set_req_mon_condition(ovnsb_idl, fdb, &fdb), + sb_table_set_req_mon_condition(ovnsb_idl, multicast_group, &mg), + sb_table_set_req_mon_condition(ovnsb_idl, dns, &dns), + sb_table_set_req_mon_condition(ovnsb_idl, controller_event, &ce), +@@ -378,6 +382,7 @@ out:; + ovsdb_idl_condition_destroy(&lf); + ovsdb_idl_condition_destroy(&ldpg); + ovsdb_idl_condition_destroy(&mb); ++ ovsdb_idl_condition_destroy(&fdb); + ovsdb_idl_condition_destroy(&mg); + ovsdb_idl_condition_destroy(&dns); + ovsdb_idl_condition_destroy(&ce); +@@ -1499,6 +1504,8 @@ struct ed_type_runtime_data { + /* Tracked data. See below for more details and comments. */ + bool tracked; + bool local_lports_changed; ++ bool localnet_learn_fdb; ++ bool localnet_learn_fdb_changed; + struct hmap tracked_dp_bindings; + + struct shash local_active_ports_ipv6_pd; +@@ -1709,6 +1716,8 @@ init_binding_ctx(struct engine_node *node, + b_ctx_out->postponed_ports = rt_data->postponed_ports; + b_ctx_out->tracked_dp_bindings = NULL; + b_ctx_out->if_mgr = ctrl_ctx->if_mgr; ++ b_ctx_out->localnet_learn_fdb = rt_data->localnet_learn_fdb; ++ b_ctx_out->localnet_learn_fdb_changed = false; + } + + static void +@@ -1766,6 +1775,7 @@ en_runtime_data_run(struct engine_node *node, void *data) + } + + binding_run(&b_ctx_in, &b_ctx_out); ++ rt_data->localnet_learn_fdb = b_ctx_out.localnet_learn_fdb; + + engine_set_node_state(node, EN_UPDATED); + } +@@ -1815,6 +1825,8 @@ runtime_data_sb_ro_handler(struct engine_node *node, void *data) + engine_ovsdb_node_get_index( + engine_get_input("SB_chassis", node), + "name"); ++ const struct sbrec_port_binding_table *pb_table = ++ EN_OVSDB_GET(engine_get_input("SB_port_binding", node)); + + if (chassis_id) { + chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id); +@@ -1829,7 +1841,7 @@ runtime_data_sb_ro_handler(struct engine_node *node, void *data) + &rt_data->lbinding_data, + chassis, + &rt_data->tracked_dp_bindings, +- sb_readonly)) { ++ pb_table, sb_readonly)) { + engine_set_node_state(node, EN_UPDATED); + rt_data->tracked = true; + } +@@ -1878,9 +1890,12 @@ runtime_data_sb_port_binding_handler(struct engine_node *node, void *data) + } + + rt_data->local_lports_changed = b_ctx_out.local_lports_changed; ++ rt_data->localnet_learn_fdb = b_ctx_out.localnet_learn_fdb; ++ rt_data->localnet_learn_fdb_changed = b_ctx_out.localnet_learn_fdb_changed; + if (b_ctx_out.related_lports_changed || + b_ctx_out.non_vif_ports_changed || + b_ctx_out.local_lports_changed || ++ b_ctx_out.localnet_learn_fdb_changed || + !hmap_is_empty(b_ctx_out.tracked_dp_bindings)) { + engine_set_node_state(node, EN_UPDATED); + } +@@ -1897,6 +1912,7 @@ runtime_data_sb_datapath_binding_handler(struct engine_node *node OVS_UNUSED, + engine_get_input("SB_datapath_binding", node)); + const struct sbrec_datapath_binding *dp; + struct ed_type_runtime_data *rt_data = data; ++ struct local_datapath *ld; + + SBREC_DATAPATH_BINDING_TABLE_FOR_EACH_TRACKED (dp, dp_table) { + if (sbrec_datapath_binding_is_deleted(dp)) { +@@ -1904,6 +1920,28 @@ runtime_data_sb_datapath_binding_handler(struct engine_node *node OVS_UNUSED, + dp->tunnel_key)) { + return false; + } ++ /* If the tunnel key got updated, get_local_datapath will not find ++ * the ld. Use get_local_datapath_no_hash which does not ++ * rely on the hash. ++ */ ++ if (sbrec_datapath_binding_is_updated( ++ dp, SBREC_DATAPATH_BINDING_COL_TUNNEL_KEY)) { ++ if (get_local_datapath_no_hash(&rt_data->local_datapaths, ++ dp->tunnel_key)) { ++ return false; ++ } ++ } ++ } else if (sbrec_datapath_binding_is_updated( ++ dp, SBREC_DATAPATH_BINDING_COL_TUNNEL_KEY) ++ && !sbrec_datapath_binding_is_new(dp)) { ++ /* If the tunnel key is updated, remove the entry (with a wrong ++ * hash) from the map. It will be (properly) added back later. ++ */ ++ if ((ld = get_local_datapath_no_hash(&rt_data->local_datapaths, ++ dp->tunnel_key))) { ++ hmap_remove(&rt_data->local_datapaths, &ld->hmap_node); ++ local_datapath_destroy(ld); ++ } + } + } + +@@ -2637,9 +2675,10 @@ ct_zones_runtime_data_handler(struct engine_node *node, void *data) + struct tracked_lport *t_lport = shash_node->data; + if (strcmp(t_lport->pb->type, "") + && strcmp(t_lport->pb->type, "localport") ++ && strcmp(t_lport->pb->type, "l3gateway") + && strcmp(t_lport->pb->type, "localnet")) { +- /* We allocate zone-id's only to VIF, localport, and localnet +- * lports. */ ++ /* We allocate zone-id's only to VIF, localport, l3gateway, ++ * and localnet lports. */ + continue; + } + +@@ -2803,12 +2842,25 @@ load_balancers_by_dp_init(const struct hmap *local_datapaths, + load_balancers_by_dp_add_one(local_datapaths, + lb->datapaths[i], lb, lbs); + } ++ /* datapath_group column is deprecated. */ + for (size_t i = 0; lb->datapath_group + && i < lb->datapath_group->n_datapaths; i++) { + load_balancers_by_dp_add_one(local_datapaths, + lb->datapath_group->datapaths[i], + lb, lbs); + } ++ for (size_t i = 0; lb->ls_datapath_group ++ && i < lb->ls_datapath_group->n_datapaths; i++) { ++ load_balancers_by_dp_add_one(local_datapaths, ++ lb->ls_datapath_group->datapaths[i], ++ lb, lbs); ++ } ++ for (size_t i = 0; lb->lr_datapath_group ++ && i < lb->lr_datapath_group->n_datapaths; i++) { ++ load_balancers_by_dp_add_one(local_datapaths, ++ lb->lr_datapath_group->datapaths[i], ++ lb, lbs); ++ } + } + return lbs; + } +@@ -3788,6 +3840,11 @@ init_lflow_ctx(struct engine_node *node, + engine_get_input("SB_port_binding", node), + "name"); + ++ struct ovsdb_idl_index *sbrec_port_binding_by_key = ++ engine_ovsdb_node_get_index( ++ engine_get_input("SB_port_binding", node), ++ "key"); ++ + struct ovsdb_idl_index *sbrec_logical_flow_by_dp = + engine_ovsdb_node_get_index( + engine_get_input("SB_logical_flow", node), +@@ -3887,6 +3944,7 @@ init_lflow_ctx(struct engine_node *node, + l_ctx_in->sbrec_logical_flow_by_logical_dp_group = + sbrec_logical_flow_by_dp_group; + l_ctx_in->sbrec_port_binding_by_name = sbrec_port_binding_by_name; ++ l_ctx_in->sbrec_port_binding_by_key = sbrec_port_binding_by_key; + l_ctx_in->sbrec_fdb_by_dp_key = sbrec_fdb_by_dp_key; + l_ctx_in->sbrec_mac_binding_by_datapath = sbrec_mac_binding_by_datapath; + l_ctx_in->sbrec_static_mac_binding_by_datapath = +@@ -3905,6 +3963,8 @@ init_lflow_ctx(struct engine_node *node, + l_ctx_in->active_tunnels = &rt_data->active_tunnels; + l_ctx_in->related_lport_ids = &rt_data->related_lports.lport_ids; + l_ctx_in->binding_lports = &rt_data->lbinding_data.lports; ++ l_ctx_in->localnet_learn_fdb = rt_data->localnet_learn_fdb; ++ l_ctx_in->localnet_learn_fdb_changed = rt_data->localnet_learn_fdb_changed; + l_ctx_in->chassis_tunnels = &non_vif_data->chassis_tunnels; + l_ctx_in->lb_hairpin_use_ct_mark = n_opts->lb_hairpin_use_ct_mark; + l_ctx_in->nd_ra_opts = &fo->nd_ra_opts; +@@ -3930,8 +3990,8 @@ en_lflow_output_init(struct engine_node *node OVS_UNUSED, + { + struct ed_type_lflow_output *data = xzalloc(sizeof *data); + ovn_desired_flow_table_init(&data->flow_table); +- ovn_extend_table_init(&data->group_table); +- ovn_extend_table_init(&data->meter_table); ++ ovn_extend_table_init(&data->group_table, "group-table", 0); ++ ovn_extend_table_init(&data->meter_table, "meter-table", 0); + objdep_mgr_init(&data->lflow_deps_mgr); + lflow_conj_ids_init(&data->conj_ids); + uuidset_init(&data->objs_processed); +@@ -4895,11 +4955,6 @@ controller_output_mac_cache_handler(struct engine_node *node, + return true; + } + +-struct ovn_controller_exit_args { +- bool *exiting; +- bool *restart; +-}; +- + /* Handles sbrec_chassis changes. + * If a new chassis is added or removed return false, so that + * flows are recomputed. For any updates, there is no need for +@@ -4974,9 +5029,7 @@ int + main(int argc, char *argv[]) + { + struct unixctl_server *unixctl; +- bool exiting; +- bool restart; +- struct ovn_controller_exit_args exit_args = {&exiting, &restart}; ++ struct ovn_exit_args exit_args = {}; + int retval; + + /* Read from system-id-override file once on startup. */ +@@ -4996,7 +5049,7 @@ main(int argc, char *argv[]) + if (retval) { + exit(EXIT_FAILURE); + } +- unixctl_command_register("exit", "", 0, 1, ovn_controller_exit, ++ unixctl_command_register("exit", "", 0, 1, ovn_exit_command_callback, + &exit_args); + + daemonize_complete(); +@@ -5508,10 +5561,8 @@ main(int argc, char *argv[]) + VLOG_INFO("OVN internal version is : [%s]", ovn_version); + + /* Main loop. */ +- exiting = false; +- restart = false; + bool sb_monitor_all = false; +- while (!exiting) { ++ while (!exit_args.exiting) { + memory_run(); + if (memory_should_report()) { + struct simap usage = SIMAP_INITIALIZER(&usage); +@@ -5645,9 +5696,20 @@ main(int argc, char *argv[]) + br_int ? br_int->name : NULL)) { + VLOG_INFO("OVS feature set changed, force recompute."); + engine_set_force_recompute(true); ++ if (ovs_feature_set_discovered()) { ++ uint32_t max_groups = ovs_feature_max_select_groups_get(); ++ uint32_t max_meters = ovs_feature_max_meters_get(); ++ struct ed_type_lflow_output *lflow_out_data = ++ engine_get_internal_data(&en_lflow_output); ++ ++ ovn_extend_table_reinit(&lflow_out_data->group_table, ++ max_groups); ++ ovn_extend_table_reinit(&lflow_out_data->meter_table, ++ max_meters); ++ } + } + +- if (br_int) { ++ if (br_int && ovs_feature_set_discovered()) { + ct_zones_data = engine_get_data(&en_ct_zones); + if (ct_zones_data && ofctrl_run(br_int, ovs_table, + &ct_zones_data->pending)) { +@@ -5809,6 +5871,13 @@ main(int argc, char *argv[]) + &runtime_data->local_datapaths, + sb_monitor_all); + } ++ if (ovs_idl_txn) { ++ update_qos(sbrec_port_binding_by_name, ovs_idl_txn, ++ ovsrec_port_by_qos, ++ ovsrec_qos_table_get(ovs_idl_loop.idl), ++ &runtime_data->qos_map, ++ ovs_table, bridge_table); ++ } + } + + if (mac_cache_data) { +@@ -5864,6 +5933,8 @@ main(int argc, char *argv[]) + if_status_mgr_run(if_mgr, binding_data, chassis, + ovsrec_interface_table_get( + ovs_idl_loop.idl), ++ sbrec_port_binding_table_get( ++ ovnsb_idl_loop.idl), + !ovnsb_idl_txn, !ovs_idl_txn); + stopwatch_stop(IF_STATUS_MGR_RUN_STOPWATCH_NAME, + time_msec()); +@@ -5941,7 +6012,7 @@ main(int argc, char *argv[]) + unixctl_server_run(unixctl); + + unixctl_server_wait(unixctl); +- if (exiting || pending_pkt.conn) { ++ if (exit_args.exiting || pending_pkt.conn) { + poll_immediate_wake(); + } + +@@ -5992,7 +6063,7 @@ loop_done: + memory_wait(); + poll_block(); + if (should_service_stop()) { +- exiting = true; ++ exit_args.exiting = true; + } + } + +@@ -6000,7 +6071,7 @@ loop_done: + engine_cleanup(); + + /* It's time to exit. Clean up the databases if we are not restarting */ +- if (!restart) { ++ if (!exit_args.restart) { + bool done = !ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl); + while (!done) { + update_sb_db(ovs_idl_loop.idl, ovnsb_idl_loop.idl, +@@ -6052,7 +6123,6 @@ loop_done: + } + + free(ovn_version); +- unixctl_server_destroy(unixctl); + lflow_destroy(); + ofctrl_destroy(); + pinctrl_destroy(); +@@ -6077,6 +6147,8 @@ loop_done: + if (cli_system_id) { + free(cli_system_id); + } ++ ovn_exit_args_finish(&exit_args); ++ unixctl_server_destroy(unixctl); + service_stop(); + ovsrcu_exit(); + +@@ -6156,6 +6228,7 @@ parse_options(int argc, char *argv[]) + break; + + case 'n': ++ free(cli_system_id); + cli_system_id = xstrdup(optarg); + break; + +@@ -6200,16 +6273,6 @@ usage(void) + exit(EXIT_SUCCESS); + } + +-static void +-ovn_controller_exit(struct unixctl_conn *conn, int argc, +- const char *argv[], void *exit_args_) +-{ +- struct ovn_controller_exit_args *exit_args = exit_args_; +- *exit_args->exiting = true; +- *exit_args->restart = argc == 2 && !strcmp(argv[1], "--restart"); +- unixctl_command_reply(conn, NULL); +-} +- + static void + ct_zone_list(struct unixctl_conn *conn, int argc OVS_UNUSED, + const char *argv[] OVS_UNUSED, void *ct_zones_) +diff --git a/controller/physical.c b/controller/physical.c +index 75257bc85..3b862ca34 100644 +--- a/controller/physical.c ++++ b/controller/physical.c +@@ -341,7 +341,7 @@ get_remote_tunnels(const struct sbrec_port_binding *binding, + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL( + &rl, "Failed to locate tunnel to reach main chassis %s " +- "for port %s. Cloning packets disabled for the chassis.", ++ "for port %s.", + binding->chassis->name, binding->logical_port); + } else { + struct tunnel *tun_elem = xmalloc(sizeof *tun_elem); +@@ -363,7 +363,7 @@ get_remote_tunnels(const struct sbrec_port_binding *binding, + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL( + &rl, "Failed to locate tunnel to reach additional chassis %s " +- "for port %s. Cloning packets disabled for the chassis.", ++ "for port %s.", + binding->additional_chassis[i]->name, binding->logical_port); + continue; + } +@@ -703,22 +703,33 @@ put_replace_chassis_mac_flows(const struct simap *ct_zones, + } + } + +-#define VLAN_80211AD_ETHTYPE 0x88a8 +-#define VLAN_80211Q_ETHTYPE 0x8100 ++#define VLAN_8021AD_ETHTYPE 0x88a8 ++#define VLAN_8021Q_ETHTYPE 0x8100 + + static void + ofpact_put_push_vlan(struct ofpbuf *ofpacts, const struct smap *options, int tag) + { + const char *ethtype_opt = options ? smap_get(options, "ethtype") : NULL; + +- int ethtype = VLAN_80211Q_ETHTYPE; ++ int ethtype = VLAN_8021Q_ETHTYPE; + if (ethtype_opt) { +- if (!strcasecmp(ethtype_opt, "802.11ad")) { +- ethtype = VLAN_80211AD_ETHTYPE; +- } else if (strcasecmp(ethtype_opt, "802.11q")) { ++ if (!strcasecmp(ethtype_opt, "802.11ad") ++ || !strcasecmp(ethtype_opt, "802.1ad")) { ++ ethtype = VLAN_8021AD_ETHTYPE; ++ } else if (!strcasecmp(ethtype_opt, "802.11q") ++ || !strcasecmp(ethtype_opt, "802.1q")) { ++ ethtype = VLAN_8021Q_ETHTYPE; ++ } else { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "Unknown port ethtype: %s", ethtype_opt); +- } ++ } ++ if (!strcasecmp(ethtype_opt, "802.11ad") ++ || !strcasecmp(ethtype_opt, "802.11q")) { ++ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); ++ VLOG_WARN_RL(&rl, "Using incorrect value ethtype: %s for either " ++ "802.1q or 802.1ad please correct this value", ++ ethtype_opt); ++ } + } + + struct ofpact_push_vlan *push_vlan; +diff --git a/controller/pinctrl.c b/controller/pinctrl.c +index ff5a3444c..ececbdb48 100644 +--- a/controller/pinctrl.c ++++ b/controller/pinctrl.c +@@ -373,9 +373,13 @@ static const struct sbrec_fdb *fdb_lookup( + uint32_t dp_key, const char *mac); + static void run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn, + struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac, ++ struct ovsdb_idl_index *sbrec_port_binding_by_key, ++ struct ovsdb_idl_index *sbrec_datapath_binding_by_key, + const struct fdb_entry *fdb_e) + OVS_REQUIRES(pinctrl_mutex); + static void run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn, ++ struct ovsdb_idl_index *sbrec_port_binding_by_key, ++ struct ovsdb_idl_index *sbrec_datapath_binding_by_key, + struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac) + OVS_REQUIRES(pinctrl_mutex); + static void wait_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn); +@@ -1280,11 +1284,25 @@ fill_ipv6_prefix_state(struct ovsdb_idl_txn *ovnsb_idl_txn, + continue; + } + ++ /* To reach this point, the port binding must be a logical router ++ * port. LRPs are configured with a single MAC that is always non-NULL. ++ * Therefore, as long as we are working with a port_binding that was ++ * inserted into the southbound database by northd, we can always ++ * safely extract pb->mac[0] since it will be non-NULL. ++ * ++ * However, if a port_binding was inserted by someone else, then we ++ * need to double-check our assumption first. ++ */ ++ if (pb->n_mac != 1) { ++ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); ++ VLOG_ERR_RL(&rl, "Port binding "UUID_FMT" has %"PRIuSIZE" MACs " ++ "instead of 1", UUID_ARGS(&pb->header_.uuid), ++ pb->n_mac); ++ continue; ++ } + struct lport_addresses c_addrs; +- for (size_t j = 0; j < pb->n_mac; j++) { +- if (extract_lsp_addresses(pb->mac[j], &c_addrs)) { +- break; +- } ++ if (!extract_lsp_addresses(pb->mac[0], &c_addrs)) { ++ continue; + } + + pfd = shash_find_data(&ipv6_prefixd, pb->logical_port); +@@ -3577,7 +3595,8 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, + chassis); + bfd_monitor_run(ovnsb_idl_txn, bfd_table, sbrec_port_binding_by_name, + chassis, active_tunnels); +- run_put_fdbs(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac); ++ run_put_fdbs(ovnsb_idl_txn, sbrec_port_binding_by_key, ++ sbrec_datapath_binding_by_key, sbrec_fdb_by_dp_key_mac); + run_activated_ports(ovnsb_idl_txn, sbrec_datapath_binding_by_key, + sbrec_port_binding_by_key, chassis); + ovs_mutex_unlock(&pinctrl_mutex); +@@ -6226,6 +6245,12 @@ pinctrl_handle_put_nd_ra_opts( + + /* Set the IPv6 payload length and calculate the ICMPv6 checksum. */ + struct ovs_16aligned_ip6_hdr *nh = dp_packet_l3(&pkt_out); ++ ++ /* Set the source to "ff02::1" if the original source is "::". */ ++ if (!memcmp(&nh->ip6_src, &in6addr_any, sizeof in6addr_any)) { ++ memcpy(&nh->ip6_src, &in6addr_all_hosts, sizeof in6addr_all_hosts); ++ } ++ + nh->ip6_plen = htons(userdata->size); + struct ovs_ra_msg *ra = dp_packet_l4(&pkt_out); + ra->icmph.icmp6_cksum = 0; +@@ -6338,26 +6363,31 @@ pinctrl_handle_empty_lb_backends_opts(struct ofpbuf *userdata) + char *protocol = NULL; + char *load_balancer = NULL; + ++ struct empty_lb_backends_event *event = NULL; ++ + while (userdata->size) { + userdata_opt = ofpbuf_try_pull(userdata, sizeof opt_hdr); + if (!userdata_opt) { +- return false; ++ goto cleanup; + } + memcpy(&opt_hdr, userdata_opt, sizeof opt_hdr); + + size_t size = ntohs(opt_hdr.size); + char *userdata_opt_data = ofpbuf_try_pull(userdata, size); + if (!userdata_opt_data) { +- return false; ++ goto cleanup; + } + switch (ntohs(opt_hdr.opt_code)) { + case EMPTY_LB_VIP: ++ free(vip); + vip = xmemdup0(userdata_opt_data, size); + break; + case EMPTY_LB_PROTOCOL: ++ free(protocol); + protocol = xmemdup0(userdata_opt_data, size); + break; + case EMPTY_LB_LOAD_BALANCER: ++ free(load_balancer); + load_balancer = xmemdup0(userdata_opt_data, size); + break; + default: +@@ -6368,36 +6398,39 @@ pinctrl_handle_empty_lb_backends_opts(struct ofpbuf *userdata) + if (!vip || !protocol || !load_balancer) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "missing lb parameters in userdata"); +- free(vip); +- free(protocol); +- free(load_balancer); +- return false; ++ goto cleanup; + } + +- struct empty_lb_backends_event *event; +- + event = pinctrl_find_empty_lb_backends_event(vip, protocol, + load_balancer, hash); +- if (!event) { +- if (hmap_count(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) >= 1000) { +- COVERAGE_INC(pinctrl_drop_controller_event); +- return false; +- } ++ if (event) { ++ goto cleanup; ++ } + +- event = xzalloc(sizeof *event); +- hmap_insert(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS], +- &event->hmap_node, hash); +- event->vip = vip; +- event->protocol = protocol; +- event->load_balancer = load_balancer; +- event->timestamp = time_msec(); +- notify_pinctrl_main(); +- } else { +- free(vip); +- free(protocol); +- free(load_balancer); ++ if (hmap_count(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) >= 1000) { ++ COVERAGE_INC(pinctrl_drop_controller_event); ++ goto cleanup; + } +- return true; ++ ++ event = xzalloc(sizeof *event); ++ hmap_insert(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS], ++ &event->hmap_node, hash); ++ event->vip = vip; ++ event->protocol = protocol; ++ event->load_balancer = load_balancer; ++ event->timestamp = time_msec(); ++ notify_pinctrl_main(); ++ ++ vip = NULL; ++ protocol = NULL; ++ load_balancer = NULL; ++ ++cleanup: ++ free(vip); ++ free(protocol); ++ free(load_balancer); ++ ++ return event != NULL; + } + + static void +@@ -7755,7 +7788,9 @@ svc_monitors_run(struct rconn *swconn, + if (svc_mon->n_success >= svc_mon->success_count) { + svc_mon->status = SVC_MON_ST_ONLINE; + svc_mon->n_success = 0; ++ svc_mon->n_failures = 0; + } ++ + if (current_time >= svc_mon->next_send_time) { + svc_monitor_send_health_check(swconn, svc_mon); + next_run_time = svc_mon->wait_time; +@@ -7767,6 +7802,7 @@ svc_monitors_run(struct rconn *swconn, + case SVC_MON_S_OFFLINE: + if (svc_mon->n_failures >= svc_mon->failure_count) { + svc_mon->status = SVC_MON_ST_OFFLINE; ++ svc_mon->n_success = 0; + svc_mon->n_failures = 0; + } + +@@ -7812,7 +7848,6 @@ pinctrl_handle_tcp_svc_check(struct rconn *swconn, + return false; + } + +- uint32_t tcp_seq = ntohl(get_16aligned_be32(&th->tcp_seq)); + uint32_t tcp_ack = ntohl(get_16aligned_be32(&th->tcp_ack)); + + if (th->tcp_dst != svc_mon->tp_src) { +@@ -7829,10 +7864,10 @@ pinctrl_handle_tcp_svc_check(struct rconn *swconn, + svc_mon->n_success++; + svc_mon->state = SVC_MON_S_ONLINE; + +- /* Send RST-ACK packet. */ +- svc_monitor_send_tcp_health_check__(swconn, svc_mon, TCP_RST | TCP_ACK, +- htonl(tcp_ack + 1), +- htonl(tcp_seq + 1), th->tcp_dst); ++ /* Send RST packet. */ ++ svc_monitor_send_tcp_health_check__(swconn, svc_mon, TCP_RST, ++ htonl(tcp_ack), ++ htonl(0), th->tcp_dst); + /* Calculate next_send_time. */ + svc_mon->next_send_time = time_msec() + svc_mon->interval; + return true; +@@ -8184,6 +8219,8 @@ fdb_lookup(struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac, uint32_t dp_key, + static void + run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn, + struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac, ++ struct ovsdb_idl_index *sbrec_port_binding_by_key, ++ struct ovsdb_idl_index *sbrec_datapath_binding_by_key, + const struct fdb_entry *fdb_e) + { + /* Convert ethernet argument to string form for database. */ +@@ -8192,14 +8229,28 @@ run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn, + ETH_ADDR_FMT, ETH_ADDR_ARGS(fdb_e->mac)); + + /* Update or add an FDB entry. */ ++ const struct sbrec_port_binding *sb_entry_pb = NULL; ++ const struct sbrec_port_binding *new_entry_pb = NULL; + const struct sbrec_fdb *sb_fdb = + fdb_lookup(sbrec_fdb_by_dp_key_mac, fdb_e->dp_key, mac_string); + if (!sb_fdb) { + sb_fdb = sbrec_fdb_insert(ovnsb_idl_txn); + sbrec_fdb_set_dp_key(sb_fdb, fdb_e->dp_key); + sbrec_fdb_set_mac(sb_fdb, mac_string); ++ } else { ++ /* check whether sb_fdb->port_key is vif or localnet type */ ++ sb_entry_pb = lport_lookup_by_key( ++ sbrec_datapath_binding_by_key, sbrec_port_binding_by_key, ++ sb_fdb->dp_key, sb_fdb->port_key); ++ new_entry_pb = lport_lookup_by_key( ++ sbrec_datapath_binding_by_key, sbrec_port_binding_by_key, ++ fdb_e->dp_key, fdb_e->port_key); ++ } ++ /* Do not have localnet overwrite a previous vif entry */ ++ if (!sb_entry_pb || !new_entry_pb || strcmp(sb_entry_pb->type, "") || ++ strcmp(new_entry_pb->type, "localnet")) { ++ sbrec_fdb_set_port_key(sb_fdb, fdb_e->port_key); + } +- sbrec_fdb_set_port_key(sb_fdb, fdb_e->port_key); + + /* For backward compatibility check if timestamp column is available + * in SB DB. */ +@@ -8210,6 +8261,8 @@ run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn, + + static void + run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn, ++ struct ovsdb_idl_index *sbrec_port_binding_by_key, ++ struct ovsdb_idl_index *sbrec_datapath_binding_by_key, + struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac) + OVS_REQUIRES(pinctrl_mutex) + { +@@ -8219,7 +8272,9 @@ run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn, + + const struct fdb_entry *fdb_e; + HMAP_FOR_EACH (fdb_e, hmap_node, &put_fdbs) { +- run_put_fdb(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac, fdb_e); ++ run_put_fdb(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac, ++ sbrec_port_binding_by_key, ++ sbrec_datapath_binding_by_key, fdb_e); + } + ovn_fdbs_flush(&put_fdbs); + } +diff --git a/controller/statctrl.c b/controller/statctrl.c +index cb1545cbb..8cce97df8 100644 +--- a/controller/statctrl.c ++++ b/controller/statctrl.c +@@ -233,7 +233,7 @@ statctrl_thread_handler(void *arg) + struct statctrl_ctx *ctx = arg; + + /* OpenFlow connection to the switch. */ +- struct rconn *swconn = rconn_create(5, 0, DSCP_DEFAULT, ++ struct rconn *swconn = rconn_create(0, 0, DSCP_DEFAULT, + 1 << OFP15_VERSION); + + while (!latch_is_set(&ctx->exit_latch)) { +diff --git a/debian/changelog b/debian/changelog +index 96d132784..479d4c944 100644 +--- a/debian/changelog ++++ b/debian/changelog +@@ -1,3 +1,15 @@ ++OVN (23.09.2-1) unstable; urgency=low ++ [ OVN team ] ++ * New upstream version ++ ++ -- OVN team Fri, 01 Dec 2023 14:41:31 -0500 ++ ++OVN (23.09.1-1) unstable; urgency=low ++ [ OVN team ] ++ * New upstream version ++ ++ -- OVN team Fri, 01 Dec 2023 14:41:31 -0500 ++ + ovn (23.09.0-1) unstable; urgency=low + + * New upstream version +diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c +index e2023c2ba..020b1d60b 100644 +--- a/ic/ovn-ic.c ++++ b/ic/ovn-ic.c +@@ -1630,13 +1630,18 @@ collect_lr_routes(struct ic_context *ctx, + const struct icnbrec_transit_switch *key; + + struct hmap *routes_ad; ++ const struct icnbrec_transit_switch *t_sw; + for (int i = 0; i < ic_lr->n_isb_pbs; i++) { + isb_pb = ic_lr->isb_pbs[i]; + key = icnbrec_transit_switch_index_init_row( + ctx->icnbrec_transit_switch_by_name); + icnbrec_transit_switch_index_set_name(key, isb_pb->transit_switch); +- ts_name = icnbrec_transit_switch_index_find( +- ctx->icnbrec_transit_switch_by_name, key)->name; ++ t_sw = icnbrec_transit_switch_index_find( ++ ctx->icnbrec_transit_switch_by_name, key); ++ if (!t_sw) { ++ continue; ++ } ++ ts_name = t_sw->name; + icnbrec_transit_switch_index_destroy_row(key); + routes_ad = shash_find_data(routes_ad_by_ts, ts_name); + if (!routes_ad) { +@@ -2216,10 +2221,19 @@ main(int argc, char *argv[]) + ovn_db_run(&ctx); + } + +- ovsdb_idl_loop_commit_and_wait(&ovnnb_idl_loop); +- ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop); +- ovsdb_idl_loop_commit_and_wait(&ovninb_idl_loop); +- ovsdb_idl_loop_commit_and_wait(&ovnisb_idl_loop); ++ int rc1 = ovsdb_idl_loop_commit_and_wait(&ovnnb_idl_loop); ++ int rc2 = ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop); ++ int rc3 = ovsdb_idl_loop_commit_and_wait(&ovninb_idl_loop); ++ int rc4 = ovsdb_idl_loop_commit_and_wait(&ovnisb_idl_loop); ++ if (!rc1 || !rc2 || !rc3 || !rc4) { ++ VLOG_DBG(" a transaction failed in: %s %s %s %s", ++ !rc1 ? "nb" : "", !rc2 ? "sb" : "", ++ !rc3 ? "ic_nb" : "", rc4 ? "ic_sb" : ""); ++ /* A transaction failed. Wake up immediately to give ++ * opportunity to send the proper transaction ++ */ ++ poll_immediate_wake(); ++ } + } else { + /* ovn-ic is paused + * - we still want to handle any db updates and update the +diff --git a/include/ovn/features.h b/include/ovn/features.h +index 3bf536127..2c47ab766 100644 +--- a/include/ovn/features.h ++++ b/include/ovn/features.h +@@ -26,6 +26,7 @@ + #define OVN_FEATURE_MAC_BINDING_TIMESTAMP "mac-binding-timestamp" + #define OVN_FEATURE_CT_LB_RELATED "ovn-ct-lb-related" + #define OVN_FEATURE_FDB_TIMESTAMP "fdb-timestamp" ++#define OVN_FEATURE_LS_DPG_COLUMN "ls-dpg-column" + + /* OVS datapath supported features. Based on availability OVN might generate + * different types of openflows. +@@ -48,5 +49,8 @@ void ovs_feature_support_destroy(void); + bool ovs_feature_is_supported(enum ovs_feature_value feature); + bool ovs_feature_support_run(const struct smap *ovs_capabilities, + const char *br_name); ++bool ovs_feature_set_discovered(void); ++uint32_t ovs_feature_max_meters_get(void); ++uint32_t ovs_feature_max_select_groups_get(void); + + #endif +diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h +index a7b64ef67..272277ec4 100644 +--- a/include/ovn/logical-fields.h ++++ b/include/ovn/logical-fields.h +@@ -77,6 +77,7 @@ enum mff_log_flags_bits { + MLF_CHECK_PORT_SEC_BIT = 12, + MLF_LOOKUP_COMMIT_ECMP_NH_BIT = 13, + MLF_USE_LB_AFF_SESSION_BIT = 14, ++ MLF_LOCALNET_BIT = 15, + }; + + /* MFF_LOG_FLAGS_REG flag assignments */ +@@ -124,6 +125,10 @@ enum mff_log_flags { + MLF_LOOKUP_COMMIT_ECMP_NH = (1 << MLF_LOOKUP_COMMIT_ECMP_NH_BIT), + + MLF_USE_LB_AFF_SESSION = (1 << MLF_USE_LB_AFF_SESSION_BIT), ++ ++ /* Indicate that the port is localnet. */ ++ MLF_LOCALNET = (1 << MLF_LOCALNET_BIT), ++ + }; + + /* OVN logical fields +diff --git a/lib/actions.c b/lib/actions.c +index b880927b6..4d408d82d 100644 +--- a/lib/actions.c ++++ b/lib/actions.c +@@ -5004,6 +5004,7 @@ encode_COMMIT_LB_AFF(const struct ovnact_commit_lb_aff *lb_aff, + ol->hard_timeout = OFP_FLOW_PERMANENT; + ol->priority = OFP_DEFAULT_PRIORITY; + ol->table_id = OFTABLE_CHK_LB_AFFINITY; ++ ol->cookie = htonll(ep->lflow_uuid.parts[0]); + + /* Match on metadata of the packet that created the new table. */ + ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec); +diff --git a/lib/extend-table.c b/lib/extend-table.c +index ebb1a054c..8e79d7177 100644 +--- a/lib/extend-table.c ++++ b/lib/extend-table.c +@@ -17,9 +17,9 @@ + #include + #include + +-#include "bitmap.h" + #include "extend-table.h" + #include "hash.h" ++#include "id-pool.h" + #include "lib/uuid.h" + #include "openvswitch/vlog.h" + +@@ -30,13 +30,29 @@ ovn_extend_table_delete_desired(struct ovn_extend_table *table, + struct ovn_extend_table_lflow_to_desired *l); + + void +-ovn_extend_table_init(struct ovn_extend_table *table) ++ovn_extend_table_init(struct ovn_extend_table *table, const char *table_name, ++ uint32_t n_ids) + { +- table->table_ids = bitmap_allocate(MAX_EXT_TABLE_ID); +- bitmap_set1(table->table_ids, 0); /* table id 0 is invalid. */ +- hmap_init(&table->desired); +- hmap_init(&table->lflow_to_desired); +- hmap_init(&table->existing); ++ *table = (struct ovn_extend_table) { ++ .name = xstrdup(table_name), ++ .n_ids = n_ids, ++ /* Table id 0 is invalid, set id-pool base to 1. */ ++ .table_ids = id_pool_create(1, n_ids), ++ .desired = HMAP_INITIALIZER(&table->desired), ++ .lflow_to_desired = HMAP_INITIALIZER(&table->lflow_to_desired), ++ .existing = HMAP_INITIALIZER(&table->existing), ++ }; ++} ++ ++void ++ovn_extend_table_reinit(struct ovn_extend_table *table, uint32_t n_ids) ++{ ++ if (n_ids != table->n_ids) { ++ ovn_extend_table_clear(table, true); ++ id_pool_destroy(table->table_ids); ++ table->table_ids = id_pool_create(1, n_ids); ++ table->n_ids = n_ids; ++ } + } + + static struct ovn_extend_table_info * +@@ -117,13 +133,13 @@ ovn_extend_table_add_desired_to_lflow(struct ovn_extend_table *table, + ovs_list_init(&l->desired); + hmap_insert(&table->lflow_to_desired, &l->hmap_node, + uuid_hash(lflow_uuid)); +- VLOG_DBG("%s: add new lflow_to_desired entry "UUID_FMT, +- __func__, UUID_ARGS(lflow_uuid)); ++ VLOG_DBG("%s: table %s: add new lflow_to_desired entry "UUID_FMT, ++ __func__, table->name, UUID_ARGS(lflow_uuid)); + } + + ovs_list_insert(&l->desired, &r->list_node); +- VLOG_DBG("%s: lflow "UUID_FMT" use new item %s, id %"PRIu32, +- __func__, UUID_ARGS(lflow_uuid), r->desired->name, ++ VLOG_DBG("%s: table %s: lflow "UUID_FMT" use new item %s, id %"PRIu32, ++ __func__, table->name, UUID_ARGS(lflow_uuid), r->desired->name, + r->desired->table_id); + } + +@@ -160,10 +176,11 @@ ovn_extend_info_add_lflow_ref(struct ovn_extend_table *table, + } + + static void +-ovn_extend_info_del_lflow_ref(struct ovn_extend_table_lflow_ref *r) ++ovn_extend_info_del_lflow_ref(struct ovn_extend_table *table, ++ struct ovn_extend_table_lflow_ref *r) + { +- VLOG_DBG("%s: name %s, lflow "UUID_FMT" n %"PRIuSIZE, __func__, +- r->desired->name, UUID_ARGS(&r->lflow_uuid), ++ VLOG_DBG("%s: table %s: name %s, lflow "UUID_FMT" n %"PRIuSIZE, __func__, ++ table->name, r->desired->name, UUID_ARGS(&r->lflow_uuid), + hmap_count(&r->desired->references)); + hmap_remove(&r->desired->references, &r->hmap_node); + ovs_list_remove(&r->list_node); +@@ -191,8 +208,8 @@ ovn_extend_table_clear(struct ovn_extend_table *table, bool existing) + if (g->peer) { + g->peer->peer = NULL; + } else { +- /* Unset the bitmap because the peer is deleted already. */ +- bitmap_set0(table->table_ids, g->table_id); ++ /* Unset the id because the peer is deleted already. */ ++ id_pool_free_id(table->table_ids, g->table_id); + } + ovn_extend_table_info_destroy(g); + } +@@ -206,7 +223,8 @@ ovn_extend_table_destroy(struct ovn_extend_table *table) + hmap_destroy(&table->lflow_to_desired); + ovn_extend_table_clear(table, true); + hmap_destroy(&table->existing); +- bitmap_free(table->table_ids); ++ id_pool_destroy(table->table_ids); ++ free(table->name); + } + + /* Remove an entry from existing table */ +@@ -221,7 +239,7 @@ ovn_extend_table_remove_existing(struct ovn_extend_table *table, + existing->peer->peer = NULL; + } else { + /* Dealloc the ID. */ +- bitmap_set0(table->table_ids, existing->table_id); ++ id_pool_free_id(table->table_ids, existing->table_id); + } + ovn_extend_table_info_destroy(existing); + } +@@ -234,15 +252,15 @@ ovn_extend_table_delete_desired(struct ovn_extend_table *table, + struct ovn_extend_table_lflow_ref *r; + LIST_FOR_EACH_SAFE (r, list_node, &l->desired) { + struct ovn_extend_table_info *e = r->desired; +- ovn_extend_info_del_lflow_ref(r); ++ ovn_extend_info_del_lflow_ref(table, r); + if (hmap_is_empty(&e->references)) { +- VLOG_DBG("%s: %s, "UUID_FMT, __func__, +- e->name, UUID_ARGS(&l->lflow_uuid)); ++ VLOG_DBG("%s: table %s: %s, "UUID_FMT, __func__, ++ table->name, e->name, UUID_ARGS(&l->lflow_uuid)); + hmap_remove(&table->desired, &e->hmap_node); + if (e->peer) { + e->peer->peer = NULL; + } else { +- bitmap_set0(table->table_ids, e->table_id); ++ id_pool_free_id(table->table_ids, e->table_id); + } + ovn_extend_table_info_destroy(e); + } +@@ -284,7 +302,7 @@ ovn_extend_table_sync(struct ovn_extend_table *table) + } + } + +-/* Assign a new table ID for the table information from the bitmap. ++/* Assign a new table ID for the table information from the ID pool. + * If it already exists, return the old ID. */ + uint32_t + ovn_extend_table_assign_id(struct ovn_extend_table *table, const char *name, +@@ -298,9 +316,9 @@ ovn_extend_table_assign_id(struct ovn_extend_table *table, const char *name, + /* Check whether we have non installed but allocated group_id. */ + HMAP_FOR_EACH_WITH_HASH (table_info, hmap_node, hash, &table->desired) { + if (!strcmp(table_info->name, name)) { +- VLOG_DBG("ovn_externd_table_assign_id: reuse old id %"PRIu32 +- " for %s, used by lflow "UUID_FMT, +- table_info->table_id, table_info->name, ++ VLOG_DBG("ovn_extend_table_assign_id: table %s: " ++ "reuse old id %"PRIu32" for %s, used by lflow "UUID_FMT, ++ table->name, table_info->table_id, table_info->name, + UUID_ARGS(&lflow_uuid)); + ovn_extend_info_add_lflow_ref(table, table_info, &lflow_uuid); + return table_info->table_id; +@@ -320,15 +338,13 @@ ovn_extend_table_assign_id(struct ovn_extend_table *table, const char *name, + + if (!existing_info) { + /* Reserve a new id. */ +- table_id = bitmap_scan(table->table_ids, 0, 1, MAX_EXT_TABLE_ID + 1); +- } ++ if (!id_pool_alloc_id(table->table_ids, &table_id)) { ++ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + +- if (table_id == MAX_EXT_TABLE_ID + 1) { +- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); +- VLOG_ERR_RL(&rl, "%"PRIu32" out of table ids.", table_id); +- return EXT_TABLE_ID_INVALID; ++ VLOG_ERR_RL(&rl, "table %s: out of table ids.", table->name); ++ return EXT_TABLE_ID_INVALID; ++ } + } +- bitmap_set1(table->table_ids, table_id); + + table_info = ovn_extend_table_info_alloc(name, table_id, existing_info, + hash); +diff --git a/lib/extend-table.h b/lib/extend-table.h +index b43a146b4..90e6e470d 100644 +--- a/lib/extend-table.h ++++ b/lib/extend-table.h +@@ -17,18 +17,21 @@ + #ifndef EXTEND_TABLE_H + #define EXTEND_TABLE_H 1 + +-#define MAX_EXT_TABLE_ID 65535 + #define EXT_TABLE_ID_INVALID 0 + + #include "openvswitch/hmap.h" + #include "openvswitch/list.h" + #include "openvswitch/uuid.h" + ++struct id_pool; ++ + /* Used to manage expansion tables associated with Flow table, + * such as the Group Table or Meter Table. */ + struct ovn_extend_table { +- unsigned long *table_ids; /* Used as a bitmap with value set +- * for allocated ids in either desired or ++ char *name; /* Used to identify this table in a user friendly way, ++ * e.g., for logging. */ ++ uint32_t n_ids; ++ struct id_pool *table_ids; /* Used to allocate ids in either desired or + * existing (or both). If the same "name" + * exists in both desired and existing tables, + * they must share the same ID. The "peer" +@@ -81,7 +84,9 @@ struct ovn_extend_table_lflow_ref { + struct ovn_extend_table_info *desired; + }; + +-void ovn_extend_table_init(struct ovn_extend_table *); ++void ovn_extend_table_init(struct ovn_extend_table *, const char *table_name, ++ uint32_t n_ids); ++void ovn_extend_table_reinit(struct ovn_extend_table *, uint32_t n_ids); + + void ovn_extend_table_destroy(struct ovn_extend_table *); + +diff --git a/lib/features.c b/lib/features.c +index d24e8f6c5..b31b5f6dd 100644 +--- a/lib/features.c ++++ b/lib/features.c +@@ -27,6 +27,7 @@ + #include "openvswitch/rconn.h" + #include "openvswitch/ofp-msgs.h" + #include "openvswitch/ofp-meter.h" ++#include "openvswitch/ofp-group.h" + #include "openvswitch/ofp-util.h" + #include "ovn/features.h" + +@@ -81,6 +82,18 @@ static struct ovs_feature all_ovs_features[] = { + /* A bitmap of OVS features that have been detected as 'supported'. */ + static uint32_t supported_ovs_features; + ++/* Last set of received feature replies. */ ++static struct ofputil_meter_features ovs_meter_features_reply; ++static struct ofputil_group_features ovs_group_features_reply; ++ ++/* Currently discovered set of features. */ ++static struct ofputil_meter_features ovs_meter_features; ++static struct ofputil_group_features ovs_group_features; ++ ++/* Number of features replies still expected to receive for the requests ++ * we sent already. */ ++static uint32_t n_features_reply_expected; ++ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); + + /* ovs-vswitchd connection. */ +@@ -145,11 +158,19 @@ ovs_feature_get_openflow_cap(const char *br_name) + + /* send new requests just after reconnect. */ + if (conn_seq_no != rconn_get_connection_seqno(swconn)) { +- /* dump datapath meter capabilities. */ ++ n_features_reply_expected = 0; ++ ++ /* Dump OpenFlow switch meter capabilities. */ + msg = ofpraw_alloc(OFPRAW_OFPST13_METER_FEATURES_REQUEST, + rconn_get_version(swconn), 0); + rconn_send(swconn, msg, NULL); ++ n_features_reply_expected++; ++ /* Dump OpenFlow switch group capabilities. */ ++ msg = ofputil_encode_group_features_request(rconn_get_version(swconn)); ++ rconn_send(swconn, msg, NULL); ++ n_features_reply_expected++; + } ++ conn_seq_no = rconn_get_connection_seqno(swconn); + + bool ret = false; + for (int i = 0; i < 50; i++) { +@@ -163,21 +184,13 @@ ovs_feature_get_openflow_cap(const char *br_name) + ofptype_decode(&type, oh); + + if (type == OFPTYPE_METER_FEATURES_STATS_REPLY) { +- struct ofputil_meter_features mf; +- ofputil_decode_meter_features(oh, &mf); +- +- bool old_state = supported_ovs_features & OVS_DP_METER_SUPPORT; +- bool new_state = mf.max_meters > 0; +- +- if (old_state != new_state) { +- ret = true; +- if (new_state) { +- supported_ovs_features |= OVS_DP_METER_SUPPORT; +- } else { +- supported_ovs_features &= ~OVS_DP_METER_SUPPORT; +- } +- } +- conn_seq_no = rconn_get_connection_seqno(swconn); ++ ofputil_decode_meter_features(oh, &ovs_meter_features_reply); ++ ovs_assert(n_features_reply_expected); ++ n_features_reply_expected--; ++ } else if (type == OFPTYPE_GROUP_FEATURES_STATS_REPLY) { ++ ofputil_decode_group_features_reply(oh, &ovs_group_features_reply); ++ ovs_assert(n_features_reply_expected); ++ n_features_reply_expected--; + } else if (type == OFPTYPE_ECHO_REQUEST) { + rconn_send(swconn, ofputil_encode_echo_reply(oh), NULL); + } +@@ -186,6 +199,26 @@ ovs_feature_get_openflow_cap(const char *br_name) + rconn_run_wait(swconn); + rconn_recv_wait(swconn); + ++ /* If all feature replies were received, update the set of supported ++ * features. */ ++ if (!n_features_reply_expected) { ++ if (memcmp(&ovs_meter_features, &ovs_meter_features_reply, ++ sizeof ovs_meter_features_reply)) { ++ ovs_meter_features = ovs_meter_features_reply; ++ if (ovs_meter_features.max_meters) { ++ supported_ovs_features |= OVS_DP_METER_SUPPORT; ++ } else { ++ supported_ovs_features &= ~OVS_DP_METER_SUPPORT; ++ } ++ ret = true; ++ } ++ if (memcmp(&ovs_group_features, &ovs_group_features_reply, ++ sizeof ovs_group_features_reply)) { ++ ovs_group_features = ovs_group_features_reply; ++ ret = true; ++ } ++ } ++ + return ret; + } + +@@ -229,3 +262,26 @@ ovs_feature_support_run(const struct smap *ovs_capabilities, + } + return updated; + } ++ ++bool ++ovs_feature_set_discovered(void) ++{ ++ /* The supported feature set has been discovered if we're connected ++ * to OVS and it replied to all our feature request messages. */ ++ return swconn && rconn_is_connected(swconn) && ++ n_features_reply_expected == 0; ++} ++ ++/* Returns the number of meters the OVS datapath supports. */ ++uint32_t ++ovs_feature_max_meters_get(void) ++{ ++ return ovs_meter_features.max_meters; ++} ++ ++/* Returns the number of select groups the OVS datapath supports. */ ++uint32_t ++ovs_feature_max_select_groups_get(void) ++{ ++ return ovs_group_features.max_groups[OFPGT11_SELECT]; ++} +diff --git a/lib/logical-fields.c b/lib/logical-fields.c +index fd509d9ee..7a1e66d0a 100644 +--- a/lib/logical-fields.c ++++ b/lib/logical-fields.c +@@ -129,6 +129,10 @@ ovn_init_symtab(struct shash *symtab) + MLF_USE_SNAT_ZONE); + expr_symtab_add_subfield(symtab, "flags.use_snat_zone", NULL, + flags_str); ++ snprintf(flags_str, sizeof flags_str, "flags[%d]", ++ MLF_LOCALNET_BIT); ++ expr_symtab_add_subfield(symtab, "flags.localnet", NULL, ++ flags_str); + + /* Connection tracking state. */ + expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false, +diff --git a/lib/memory-trim.c b/lib/memory-trim.c +index d8c6b4743..be8983fa7 100644 +--- a/lib/memory-trim.c ++++ b/lib/memory-trim.c +@@ -71,13 +71,14 @@ memory_trimmer_can_run(struct memory_trimmer *mt) + } + + long long int now = time_msec(); +- if (now < mt->last_active_ms || now < mt->trim_timeout_ms) { ++ if (now < mt->last_active_ms) { + VLOG_WARN_RL(&rl, "Detected last active timestamp overflow"); + mt->recently_active = false; + return true; + } + +- if (now - mt->trim_timeout_ms >= mt->last_active_ms) { ++ if (now > mt->trim_timeout_ms ++ && now - mt->trim_timeout_ms >= mt->last_active_ms) { + VLOG_INFO_RL(&rl, "Detected inactivity " + "(last active %lld ms ago): trimming memory", + now - mt->last_active_ms); +diff --git a/lib/ovn-util.c b/lib/ovn-util.c +index ffe295696..33105202f 100644 +--- a/lib/ovn-util.c ++++ b/lib/ovn-util.c +@@ -1302,3 +1302,34 @@ sorted_array_apply_diff(const struct sorted_array *a1, + apply_callback(arg, a2->arr[idx2], false); + } + } ++ ++/* Call for the unixctl command that will store the connection and ++ * set the appropriate conditions. */ ++void ++ovn_exit_command_callback(struct unixctl_conn *conn, int argc, ++ const char *argv[], void *exit_args_) ++{ ++ struct ovn_exit_args *exit_args = exit_args_; ++ ++ exit_args->n_conns++; ++ exit_args->conns = xrealloc(exit_args->conns, ++ exit_args->n_conns * sizeof *exit_args->conns); ++ ++ exit_args->exiting = true; ++ exit_args->conns[exit_args->n_conns - 1] = conn; ++ ++ if (!exit_args->restart) { ++ exit_args->restart = argc == 2 && !strcmp(argv[1], "--restart"); ++ } ++} ++ ++/* Reply to all waiting unixctl connections and free the connection array. ++ * This function should be called after the heaviest cleanup has finished. */ ++void ++ovn_exit_args_finish(struct ovn_exit_args *exit_args) ++{ ++ for (size_t i = 0; i < exit_args->n_conns; i++) { ++ unixctl_command_reply(exit_args->conns[i], NULL); ++ } ++ free(exit_args->conns); ++} +diff --git a/lib/ovn-util.h b/lib/ovn-util.h +index 16f18353d..bff50dbde 100644 +--- a/lib/ovn-util.h ++++ b/lib/ovn-util.h +@@ -474,4 +474,17 @@ void sorted_array_apply_diff(const struct sorted_array *a1, + const char *item, + bool add), + const void *arg); ++ ++/* Utilities around properly handling exit command. */ ++struct ovn_exit_args { ++ struct unixctl_conn **conns; ++ size_t n_conns; ++ bool exiting; ++ bool restart; ++}; ++ ++void ovn_exit_command_callback(struct unixctl_conn *conn, int argc, ++ const char *argv[], void *exit_args_); ++void ovn_exit_args_finish(struct ovn_exit_args *exit_args); ++ + #endif /* OVN_UTIL_H */ +diff --git a/northd/en-lb-data.c b/northd/en-lb-data.c +index 250cec848..d06f46a54 100644 +--- a/northd/en-lb-data.c ++++ b/northd/en-lb-data.c +@@ -144,6 +144,12 @@ lb_data_load_balancer_handler(struct engine_node *node, void *data) + + const struct nbrec_load_balancer *tracked_lb; + NBREC_LOAD_BALANCER_TABLE_FOR_EACH_TRACKED (tracked_lb, nb_lb_table) { ++ /* "New" + "Deleted" is a no-op. */ ++ if (nbrec_load_balancer_is_new(tracked_lb) ++ && nbrec_load_balancer_is_deleted(tracked_lb)) { ++ continue; ++ } ++ + struct ovn_northd_lb *lb; + if (nbrec_load_balancer_is_new(tracked_lb)) { + /* New load balancer. */ +@@ -153,19 +159,22 @@ lb_data_load_balancer_handler(struct engine_node *node, void *data) + add_crupdated_lb_to_tracked_data(lb, trk_lb_data, + lb->health_checks); + trk_lb_data->has_routable_lb |= lb->routable; +- } else if (nbrec_load_balancer_is_deleted(tracked_lb)) { +- lb = ovn_northd_lb_find(&lb_data->lbs, +- &tracked_lb->header_.uuid); +- ovs_assert(lb); ++ continue; ++ } ++ ++ /* Protect against "spurious" deletes reported by the IDL. */ ++ lb = ovn_northd_lb_find(&lb_data->lbs, &tracked_lb->header_.uuid); ++ if (!lb) { ++ continue; ++ } ++ ++ if (nbrec_load_balancer_is_deleted(tracked_lb)) { + hmap_remove(&lb_data->lbs, &lb->hmap_node); + add_deleted_lb_to_tracked_data(lb, trk_lb_data, + lb->health_checks); + trk_lb_data->has_routable_lb |= lb->routable; + } else { + /* Load balancer updated. */ +- lb = ovn_northd_lb_find(&lb_data->lbs, +- &tracked_lb->header_.uuid); +- ovs_assert(lb); + bool health_checks = lb->health_checks; + struct sset old_ips_v4 = SSET_INITIALIZER(&old_ips_v4); + struct sset old_ips_v6 = SSET_INITIALIZER(&old_ips_v6); +@@ -217,6 +226,12 @@ lb_data_load_balancer_group_handler(struct engine_node *node, void *data) + const struct nbrec_load_balancer_group *tracked_lb_group; + NBREC_LOAD_BALANCER_GROUP_TABLE_FOR_EACH_TRACKED (tracked_lb_group, + nb_lbg_table) { ++ /* "New" + "Deleted" is a no-op. */ ++ if (nbrec_load_balancer_group_is_new(tracked_lb_group) ++ && nbrec_load_balancer_group_is_deleted(tracked_lb_group)) { ++ continue; ++ } ++ + if (nbrec_load_balancer_group_is_new(tracked_lb_group)) { + struct ovn_lb_group *lb_group = + create_lb_group(tracked_lb_group, &lb_data->lbs, +@@ -228,21 +243,22 @@ lb_data_load_balancer_group_handler(struct engine_node *node, void *data) + } + + trk_lb_data->has_routable_lb |= lb_group->has_routable_lb; +- } else if (nbrec_load_balancer_group_is_deleted(tracked_lb_group)) { +- struct ovn_lb_group *lb_group; +- lb_group = ovn_lb_group_find(&lb_data->lbgrps, +- &tracked_lb_group->header_.uuid); +- ovs_assert(lb_group); ++ continue; ++ } ++ ++ /* Protect against "spurious" deletes reported by the IDL. */ ++ struct ovn_lb_group *lb_group; ++ lb_group = ovn_lb_group_find(&lb_data->lbgrps, ++ &tracked_lb_group->header_.uuid); ++ if (!lb_group) { ++ continue; ++ } ++ ++ if (nbrec_load_balancer_group_is_deleted(tracked_lb_group)) { + hmap_remove(&lb_data->lbgrps, &lb_group->hmap_node); + add_deleted_lbgrp_to_tracked_data(lb_group, trk_lb_data); + trk_lb_data->has_routable_lb |= lb_group->has_routable_lb; + } else { +- +- struct ovn_lb_group *lb_group; +- lb_group = ovn_lb_group_find(&lb_data->lbgrps, +- &tracked_lb_group->header_.uuid); +- ovs_assert(lb_group); +- + /* Determine the lbs which are added or deleted for this + * lb group and add them to tracked data. + * Eg. If an lb group lbg1 before the update had [lb1, lb2, lb3] +diff --git a/northd/en-sync-sb.c b/northd/en-sync-sb.c +index aae396a43..2ec3bf54f 100644 +--- a/northd/en-sync-sb.c ++++ b/northd/en-sync-sb.c +@@ -220,7 +220,8 @@ en_sync_to_sb_lb_run(struct engine_node *node, void *data OVS_UNUSED) + struct northd_data *northd_data = engine_get_input_data("northd", node); + + sync_lbs(eng_ctx->ovnsb_idl_txn, sb_load_balancer_table, +- &northd_data->ls_datapaths, &northd_data->lb_datapaths_map); ++ &northd_data->ls_datapaths, &northd_data->lr_datapaths, ++ &northd_data->lb_datapaths_map, &northd_data->features); + engine_set_node_state(node, EN_UPDATED); + } + +@@ -248,6 +249,23 @@ sync_to_sb_lb_northd_handler(struct engine_node *node, void *data OVS_UNUSED) + return true; + } + ++bool ++sync_to_sb_lb_sb_load_balancer(struct engine_node *node, void *data OVS_UNUSED) ++{ ++ const struct sbrec_load_balancer_table *sb_load_balancer_table = ++ EN_OVSDB_GET(engine_get_input("SB_load_balancer", node)); ++ ++ /* The only reason to handle SB.Load_Balancer updates is to detect ++ * spurious records being created in clustered databases due to ++ * lack of indexing on the SB.Load_Balancer table. All other changes ++ * are valid and performed by northd, the only write-client for ++ * this table. */ ++ if (check_sb_lb_duplicates(sb_load_balancer_table)) { ++ return false; ++ } ++ return true; ++} ++ + /* sync_to_sb_pb engine node functions. + * This engine node syncs the SB Port Bindings (partly). + * en_northd engine create the SB Port binding rows and +diff --git a/northd/en-sync-sb.h b/northd/en-sync-sb.h +index f08565eee..3bcbb8259 100644 +--- a/northd/en-sync-sb.h ++++ b/northd/en-sync-sb.h +@@ -21,6 +21,8 @@ void *en_sync_to_sb_lb_init(struct engine_node *, struct engine_arg *); + void en_sync_to_sb_lb_run(struct engine_node *, void *data); + void en_sync_to_sb_lb_cleanup(void *data); + bool sync_to_sb_lb_northd_handler(struct engine_node *, void *data OVS_UNUSED); ++bool sync_to_sb_lb_sb_load_balancer(struct engine_node *, ++ void *data OVS_UNUSED); + + void *en_sync_to_sb_pb_init(struct engine_node *, struct engine_arg *); + void en_sync_to_sb_pb_run(struct engine_node *, void *data); +diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c +index 8b0817117..04df0b06c 100644 +--- a/northd/inc-proc-northd.c ++++ b/northd/inc-proc-northd.c +@@ -230,7 +230,8 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb, + + engine_add_input(&en_sync_to_sb_lb, &en_northd, + sync_to_sb_lb_northd_handler); +- engine_add_input(&en_sync_to_sb_lb, &en_sb_load_balancer, NULL); ++ engine_add_input(&en_sync_to_sb_lb, &en_sb_load_balancer, ++ sync_to_sb_lb_sb_load_balancer); + + engine_add_input(&en_sync_to_sb_pb, &en_northd, + sync_to_sb_pb_northd_handler); +diff --git a/northd/northd.c b/northd/northd.c +index 04afe62a8..8aeca33f8 100644 +--- a/northd/northd.c ++++ b/northd/northd.c +@@ -491,6 +491,15 @@ build_chassis_features(const struct sbrec_chassis_table *sbrec_chassis_table, + chassis_features->fdb_timestamp) { + chassis_features->fdb_timestamp = false; + } ++ ++ bool ls_dpg_column = ++ smap_get_bool(&chassis->other_config, ++ OVN_FEATURE_LS_DPG_COLUMN, ++ false); ++ if (!ls_dpg_column && ++ chassis_features->ls_dpg_column) { ++ chassis_features->ls_dpg_column = false; ++ } + } + } + +@@ -4511,6 +4520,7 @@ struct sb_lb { + + const struct sbrec_load_balancer *slb; + struct ovn_dp_group *dpg; ++ struct ovn_dp_group *lr_dpg; + struct uuid lb_uuid; + }; + +@@ -4533,10 +4543,13 @@ find_slb_in_sb_lbs(struct hmap *sb_lbs, const struct uuid *lb_uuid) + void + sync_lbs(struct ovsdb_idl_txn *ovnsb_txn, + const struct sbrec_load_balancer_table *sbrec_load_balancer_table, +- struct ovn_datapaths *ls_datapaths, struct hmap *lb_dps_map) ++ struct ovn_datapaths *ls_datapaths, ++ struct ovn_datapaths *lr_datapaths, ++ struct hmap *lb_dps_map, ++ struct chassis_features *chassis_features) + { +- struct hmap dp_groups = HMAP_INITIALIZER(&dp_groups); +- size_t bitmap_len = ods_size(ls_datapaths); ++ struct hmap ls_dp_groups = HMAP_INITIALIZER(&ls_dp_groups); ++ struct hmap lr_dp_groups = HMAP_INITIALIZER(&lr_dp_groups); + struct ovn_lb_datapaths *lb_dps; + struct hmap sb_lbs = HMAP_INITIALIZER(&sb_lbs); + +@@ -4554,7 +4567,8 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn, + } + + /* Delete any SB load balancer entries that refer to NB load balancers +- * that don't exist anymore or are not applied to switches anymore. ++ * that don't exist anymore or are not applied to switches/routers ++ * anymore. + * + * There is also a special case in which duplicate LBs might be created + * in the SB, e.g., due to the fact that OVSDB only ensures +@@ -4562,7 +4576,8 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn, + * are not indexed in any way. + */ + lb_dps = ovn_lb_datapaths_find(lb_dps_map, &lb_uuid); +- if (!lb_dps || !lb_dps->n_nb_ls || !hmapx_add(&existing_lbs, lb_dps)) { ++ if (!lb_dps || (!lb_dps->n_nb_ls && !lb_dps->n_nb_lr) || ++ !hmapx_add(&existing_lbs, lb_dps)) { + sbrec_load_balancer_delete(sbrec_lb); + continue; + } +@@ -4573,12 +4588,26 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn, + hmap_insert(&sb_lbs, &sb_lb->hmap_node, uuid_hash(&lb_uuid)); + + /* Find or create datapath group for this load balancer. */ +- sb_lb->dpg = ovn_dp_group_get_or_create(ovnsb_txn, &dp_groups, +- sb_lb->slb->datapath_group, +- lb_dps->n_nb_ls, +- lb_dps->nb_ls_map, +- bitmap_len, true, +- ls_datapaths, NULL); ++ if (lb_dps->n_nb_ls) { ++ struct sbrec_logical_dp_group *ls_datapath_group ++ = chassis_features->ls_dpg_column ++ ? sb_lb->slb->ls_datapath_group ++ : sb_lb->slb->datapath_group; /* deprecated */ ++ sb_lb->dpg = ovn_dp_group_get_or_create( ++ ovnsb_txn, &ls_dp_groups, ++ ls_datapath_group, ++ lb_dps->n_nb_ls, lb_dps->nb_ls_map, ++ ods_size(ls_datapaths), true, ++ ls_datapaths, NULL); ++ } ++ if (lb_dps->n_nb_lr) { ++ sb_lb->lr_dpg = ovn_dp_group_get_or_create( ++ ovnsb_txn, &lr_dp_groups, ++ sb_lb->slb->lr_datapath_group, ++ lb_dps->n_nb_lr, lb_dps->nb_lr_map, ++ ods_size(lr_datapaths), false, ++ NULL, lr_datapaths); ++ } + } + hmapx_destroy(&existing_lbs); + +@@ -4586,7 +4615,7 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn, + * the SB load balancer columns. */ + HMAP_FOR_EACH (lb_dps, hmap_node, lb_dps_map) { + +- if (!lb_dps->n_nb_ls) { ++ if (!lb_dps->n_nb_ls && !lb_dps->n_nb_lr) { + continue; + } + +@@ -4599,8 +4628,8 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn, + + struct sb_lb *sb_lb = find_slb_in_sb_lbs(&sb_lbs, + &lb_dps->lb->nlb->header_.uuid); +- ovs_assert(!sb_lb || (sb_lb->slb && sb_lb->dpg)); +- struct ovn_dp_group *lb_dpg = NULL; ++ ovs_assert(!sb_lb || (sb_lb->slb && (sb_lb->dpg || sb_lb->lr_dpg))); ++ struct ovn_dp_group *lb_dpg = NULL, *lb_lr_dpg = NULL; + if (!sb_lb) { + sbrec_lb = sbrec_load_balancer_insert(ovnsb_txn); + char *lb_id = xasprintf( +@@ -4612,15 +4641,29 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn, + } else { + sbrec_lb = sb_lb->slb; + lb_dpg = sb_lb->dpg; ++ lb_lr_dpg = sb_lb->lr_dpg; + } + + /* Find or create datapath group for this load balancer. */ +- if (!lb_dpg) { +- lb_dpg = ovn_dp_group_get_or_create(ovnsb_txn, &dp_groups, +- sbrec_lb->datapath_group, +- lb_dps->n_nb_ls, +- lb_dps->nb_ls_map, bitmap_len, +- true, ls_datapaths, NULL); ++ if (!lb_dpg && lb_dps->n_nb_ls) { ++ struct sbrec_logical_dp_group *ls_datapath_group ++ = chassis_features->ls_dpg_column ++ ? sbrec_lb->ls_datapath_group ++ : sbrec_lb->datapath_group; /* deprecated */ ++ lb_dpg = ovn_dp_group_get_or_create( ++ ovnsb_txn, &ls_dp_groups, ++ ls_datapath_group, ++ lb_dps->n_nb_ls, lb_dps->nb_ls_map, ++ ods_size(ls_datapaths), true, ++ ls_datapaths, NULL); ++ } ++ if (!lb_lr_dpg && lb_dps->n_nb_lr) { ++ lb_lr_dpg = ovn_dp_group_get_or_create( ++ ovnsb_txn, &lr_dp_groups, ++ sbrec_lb->lr_datapath_group, ++ lb_dps->n_nb_lr, lb_dps->nb_lr_map, ++ ods_size(lr_datapaths), false, ++ NULL, lr_datapaths); + } + + /* Update columns. */ +@@ -4628,7 +4671,23 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn, + sbrec_load_balancer_set_vips(sbrec_lb, + ovn_northd_lb_get_vips(lb_dps->lb)); + sbrec_load_balancer_set_protocol(sbrec_lb, lb_dps->lb->nlb->protocol); +- sbrec_load_balancer_set_datapath_group(sbrec_lb, lb_dpg->dp_group); ++ ++ if (chassis_features->ls_dpg_column) { ++ sbrec_load_balancer_set_ls_datapath_group( ++ sbrec_lb, lb_dpg ? lb_dpg->dp_group : NULL ++ ); ++ sbrec_load_balancer_set_datapath_group(sbrec_lb, NULL); ++ } else { ++ /* datapath_group column is deprecated. */ ++ sbrec_load_balancer_set_ls_datapath_group(sbrec_lb, NULL); ++ sbrec_load_balancer_set_datapath_group( ++ sbrec_lb, lb_dpg ? lb_dpg->dp_group : NULL ++ ); ++ } ++ ++ sbrec_load_balancer_set_lr_datapath_group( ++ sbrec_lb, lb_lr_dpg ? lb_lr_dpg->dp_group : NULL ++ ); + sbrec_load_balancer_set_options(sbrec_lb, &options); + /* Clearing 'datapaths' column, since 'dp_group' is in use. */ + sbrec_load_balancer_set_datapaths(sbrec_lb, NULL, 0); +@@ -4636,11 +4695,17 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn, + } + + struct ovn_dp_group *dpg; +- HMAP_FOR_EACH_POP (dpg, node, &dp_groups) { ++ HMAP_FOR_EACH_POP (dpg, node, &ls_dp_groups) { ++ bitmap_free(dpg->bitmap); ++ free(dpg); ++ } ++ hmap_destroy(&ls_dp_groups); ++ ++ HMAP_FOR_EACH_POP (dpg, node, &lr_dp_groups) { + bitmap_free(dpg->bitmap); + free(dpg); + } +- hmap_destroy(&dp_groups); ++ hmap_destroy(&lr_dp_groups); + + struct sb_lb *sb_lb; + HMAP_FOR_EACH_POP (sb_lb, hmap_node, &sb_lbs) { +@@ -4659,6 +4724,33 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn, + sbrec_datapath_binding_set_load_balancers(od->sb, NULL, 0); + } + } ++ HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) { ++ ovs_assert(od->nbr); ++ ++ if (od->sb->n_load_balancers) { ++ sbrec_datapath_binding_set_load_balancers(od->sb, NULL, 0); ++ } ++ } ++} ++ ++bool ++check_sb_lb_duplicates(const struct sbrec_load_balancer_table *table) ++{ ++ struct sset existing_nb_lb_uuids = ++ SSET_INITIALIZER(&existing_nb_lb_uuids); ++ const struct sbrec_load_balancer *sbrec_lb; ++ bool duplicates = false; ++ ++ SBREC_LOAD_BALANCER_TABLE_FOR_EACH (sbrec_lb, table) { ++ const char *nb_lb_uuid = smap_get(&sbrec_lb->external_ids, "lb_id"); ++ if (nb_lb_uuid && !sset_add(&existing_nb_lb_uuids, nb_lb_uuid)) { ++ duplicates = true; ++ break; ++ } ++ } ++ ++ sset_destroy(&existing_nb_lb_uuids); ++ return duplicates; + } + + /* Syncs the SB port binding for the ovn_port 'op'. Caller should make sure +@@ -5089,23 +5181,21 @@ lsp_can_be_inc_processed(const struct nbrec_logical_switch_port *nbsp) + } + + static bool +-ls_port_has_changed(const struct nbrec_logical_switch_port *old, +- const struct nbrec_logical_switch_port *new) ++ls_port_has_changed(const struct nbrec_logical_switch_port *new) + { +- if (old != new) { +- return true; +- } + /* XXX: Need a better OVSDB IDL interface for this check. */ + return (nbrec_logical_switch_port_row_get_seqno(new, + OVSDB_IDL_CHANGE_MODIFY) > 0); + } + + static struct ovn_port * +-ovn_port_find_in_datapath(struct ovn_datapath *od, const char *name) ++ovn_port_find_in_datapath(struct ovn_datapath *od, ++ const struct nbrec_logical_switch_port *nbsp) + { + struct ovn_port *op; +- HMAP_FOR_EACH_WITH_HASH (op, dp_node, hash_string(name, 0), &od->ports) { +- if (!strcmp(op->key, name)) { ++ HMAP_FOR_EACH_WITH_HASH (op, dp_node, hash_string(nbsp->name, 0), ++ &od->ports) { ++ if (!strcmp(op->key, nbsp->name) && op->nbsp == nbsp) { + return op; + } + } +@@ -5290,7 +5380,7 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn, + /* Compare the individual ports in the old and new Logical Switches */ + for (size_t j = 0; j < changed_ls->n_ports; ++j) { + struct nbrec_logical_switch_port *new_nbsp = changed_ls->ports[j]; +- op = ovn_port_find_in_datapath(od, new_nbsp->name); ++ op = ovn_port_find_in_datapath(od, new_nbsp); + + if (!op) { + if (!lsp_can_be_inc_processed(new_nbsp)) { +@@ -5307,7 +5397,7 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn, + } + ovs_list_push_back(&ls_change->added_ports, + &op->list); +- } else if (ls_port_has_changed(op->nbsp, new_nbsp)) { ++ } else if (ls_port_has_changed(new_nbsp)) { + /* Existing port updated */ + bool temp = false; + if (lsp_is_type_changed(op->sb, new_nbsp, &temp) || +@@ -5580,9 +5670,9 @@ northd_handle_sb_port_binding_changes( + * notification of that transaction, and we can ignore in this + * case. Fallback to recompute otherwise, to avoid dangling + * sb idl pointers and other unexpected behavior. */ +- if (op) { +- VLOG_WARN_RL(&rl, "A port-binding for %s is deleted but the " +- "LSP still exists.", pb->logical_port); ++ if (op && op->sb == pb) { ++ VLOG_WARN_RL(&rl, "A port-binding for %s is deleted but " ++ "the LSP still exists.", pb->logical_port); + return false; + } + } else { +@@ -6954,6 +7044,9 @@ build_lswitch_learn_fdb_op( + ds_clear(match); + ds_clear(actions); + ds_put_format(match, "inport == %s", op->json_key); ++ if (lsp_is_localnet(op->nbsp)) { ++ ds_put_cstr(actions, "flags.localnet = 1; "); ++ } + ds_put_format(actions, REGBIT_LKUP_FDB + " = lookup_fdb(inport, eth.src); next;"); + ovn_lflow_add_with_lport_and_hint(lflows, op->od, +@@ -9307,6 +9400,37 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op, + } + } + ++ struct shash_node *snat_snode; ++ SHASH_FOR_EACH (snat_snode, &op->od->snat_ips) { ++ struct ovn_snat_ip *snat_ip = snat_snode->data; ++ ++ if (ovs_list_is_empty(&snat_ip->snat_entries)) { ++ continue; ++ } ++ ++ struct ovn_nat *nat_entry = ++ CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries), ++ struct ovn_nat, ext_addr_list_node); ++ const struct nbrec_nat *nat = nat_entry->nb; ++ ++ /* Check if the ovn port has a network configured on which we could ++ * expect ARP requests/NS for the SNAT external_ip. ++ */ ++ if (nat_entry_is_v6(nat_entry)) { ++ if (!sset_contains(&op->od->lb_ips->ips_v6, nat->external_ip)) { ++ build_lswitch_rport_arp_req_flow( ++ nat->external_ip, AF_INET6, sw_op, sw_od, 80, lflows, ++ stage_hint); ++ } ++ } else { ++ if (!sset_contains(&op->od->lb_ips->ips_v4, nat->external_ip)) { ++ build_lswitch_rport_arp_req_flow( ++ nat->external_ip, AF_INET, sw_op, sw_od, 80, lflows, ++ stage_hint); ++ } ++ } ++ } ++ + for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { + build_lswitch_rport_arp_req_flow( + op->lrp_networks.ipv4_addrs[i].addr_s, AF_INET, sw_op, sw_od, 80, +@@ -13047,9 +13171,9 @@ build_neigh_learning_flows_for_lrouter( + * address, the all-nodes multicast address. */ + ds_clear(actions); + ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT +- " = lookup_nd(inport, ip6.src, nd.tll); " ++ " = lookup_nd(inport, nd.target, nd.tll); " + REGBIT_LOOKUP_NEIGHBOR_IP_RESULT +- " = lookup_nd_ip(inport, ip6.src); next;"); ++ " = lookup_nd_ip(inport, nd.target); next;"); + ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 110, + "nd_na && ip6.src == fe80::/10 && ip6.dst == ff00::/8", + ds_cstr(actions)); +@@ -13895,7 +14019,15 @@ build_icmperr_pkt_big_flows(struct ovn_port *op, int mtu, struct hmap *lflows, + outport->json_key) + : NULL; + +- if (op->lrp_networks.ipv4_addrs) { ++ char *ip4_src = NULL; ++ ++ if (outport && outport->lrp_networks.ipv4_addrs) { ++ ip4_src = outport->lrp_networks.ipv4_addrs[0].addr_s; ++ } else if (op->lrp_networks.ipv4_addrs) { ++ ip4_src = op->lrp_networks.ipv4_addrs[0].addr_s; ++ } ++ ++ if (ip4_src) { + ds_clear(match); + ds_put_format(match, "inport == %s && %sip4 && "REGBIT_PKT_LARGER + " && "REGBIT_EGRESS_LOOPBACK" == 0", op->json_key, +@@ -13915,9 +14047,8 @@ build_icmperr_pkt_big_flows(struct ovn_port *op, int mtu, struct hmap *lflows, + "icmp4.code = 4; /* Frag Needed and DF was Set. */ " + "icmp4.frag_mtu = %d; " + "next(pipeline=ingress, table=%d); };", +- op->lrp_networks.ea_s, +- op->lrp_networks.ipv4_addrs[0].addr_s, +- mtu, ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); ++ op->lrp_networks.ea_s, ip4_src, mtu, ++ ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); + ovn_lflow_add_with_hint__(lflows, op->od, stage, 150, + ds_cstr(match), ds_cstr(actions), + NULL, +@@ -13928,7 +14059,15 @@ build_icmperr_pkt_big_flows(struct ovn_port *op, int mtu, struct hmap *lflows, + &op->nbrp->header_); + } + +- if (op->lrp_networks.ipv6_addrs) { ++ char *ip6_src = NULL; ++ ++ if (outport && outport->lrp_networks.ipv6_addrs) { ++ ip6_src = outport->lrp_networks.ipv6_addrs[0].addr_s; ++ } else if (op->lrp_networks.ipv6_addrs) { ++ ip6_src = op->lrp_networks.ipv6_addrs[0].addr_s; ++ } ++ ++ if (ip6_src) { + ds_clear(match); + ds_put_format(match, "inport == %s && %sip6 && "REGBIT_PKT_LARGER + " && "REGBIT_EGRESS_LOOPBACK" == 0", op->json_key, +@@ -13948,9 +14087,8 @@ build_icmperr_pkt_big_flows(struct ovn_port *op, int mtu, struct hmap *lflows, + "icmp6.code = 0; " + "icmp6.frag_mtu = %d; " + "next(pipeline=ingress, table=%d); };", +- op->lrp_networks.ea_s, +- op->lrp_networks.ipv6_addrs[0].addr_s, +- mtu, ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); ++ op->lrp_networks.ea_s, ip6_src, mtu, ++ ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); + ovn_lflow_add_with_hint__(lflows, op->od, stage, 150, + ds_cstr(match), ds_cstr(actions), + NULL, +@@ -15217,6 +15355,7 @@ build_lrouter_out_snat_in_czone_flow(struct hmap *lflows, + ds_put_format(&zone_actions, "eth.src = "ETH_ADDR_FMT"; ", + ETH_ADDR_ARGS(mac)); + } ++ ds_put_format(match, " && (!ct.trk || !ct.rpl)"); + + ds_put_cstr(&zone_actions, REGBIT_DST_NAT_IP_LOCAL" = 0; "); + +@@ -15275,10 +15414,8 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od, + ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", + ETH_ADDR_ARGS(mac)); + } +- } else { +- /* Gateway router. */ +- ds_put_cstr(match, " && (!ct.trk || !ct.rpl)"); + } ++ ds_put_cstr(match, " && (!ct.trk || !ct.rpl)"); + + ds_put_format(actions, "ct_snat(%s", nat->external_ip); + if (nat->external_port_range[0]) { +@@ -17655,6 +17792,7 @@ northd_init(struct northd_data *data) + .mac_binding_timestamp = true, + .ct_lb_related = true, + .fdb_timestamp = true, ++ .ls_dpg_column = true, + }; + data->ovn_internal_version_changed = false; + sset_init(&data->svc_monitor_lsps); +@@ -18051,13 +18189,15 @@ static void + handle_cr_port_binding_changes(const struct sbrec_port_binding *sb, + struct ovn_port *orp) + { ++ const struct nbrec_logical_router_port *nbrec_lrp = orp->l3dgw_port->nbrp; ++ + if (sb->chassis) { +- nbrec_logical_router_port_update_status_setkey( +- orp->l3dgw_port->nbrp, "hosting-chassis", +- sb->chassis->name); +- } else { +- nbrec_logical_router_port_update_status_delkey( +- orp->l3dgw_port->nbrp, "hosting-chassis"); ++ nbrec_logical_router_port_update_status_setkey(nbrec_lrp, ++ "hosting-chassis", ++ sb->chassis->name); ++ } else if (smap_get(&nbrec_lrp->status, "hosting-chassis")) { ++ nbrec_logical_router_port_update_status_delkey(nbrec_lrp, ++ "hosting-chassis"); + } + } + +diff --git a/northd/northd.h b/northd/northd.h +index 4d030b10d..76dfc392d 100644 +--- a/northd/northd.h ++++ b/northd/northd.h +@@ -70,6 +70,7 @@ struct chassis_features { + bool mac_binding_timestamp; + bool ct_lb_related; + bool fdb_timestamp; ++ bool ls_dpg_column; + }; + + /* A collection of datapaths. E.g. all logical switch datapaths, or all +@@ -365,7 +366,11 @@ const char *northd_get_svc_monitor_mac(void); + const struct ovn_datapath *northd_get_datapath_for_port( + const struct hmap *ls_ports, const char *port_name); + void sync_lbs(struct ovsdb_idl_txn *, const struct sbrec_load_balancer_table *, +- struct ovn_datapaths *ls_datapaths, struct hmap *lbs); ++ struct ovn_datapaths *ls_datapaths, ++ struct ovn_datapaths *lr_datapaths, ++ struct hmap *lbs, ++ struct chassis_features *chassis_features); ++bool check_sb_lb_duplicates(const struct sbrec_load_balancer_table *); + + void sync_pbs(struct ovsdb_idl_txn *, struct hmap *ls_ports); + bool sync_pbs_for_northd_ls_changes(struct tracked_ls_changes *); +diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml +index df8ed6750..97718821f 100644 +--- a/northd/ovn-northd.8.xml ++++ b/northd/ovn-northd.8.xml +@@ -399,14 +399,16 @@ +

+ This table looks up the MAC learning table of the logical switch + datapath to check if the port-mac pair is present +- or not. MAC is learnt only for logical switch VIF ports whose +- port security is disabled and 'unknown' address set. ++ or not. MAC is learnt for logical switch VIF ports whose ++ port security is disabled and 'unknown' address setn as well as ++ for localnet ports with option localnet_learn_fdb. A localnet ++ port entry does not overwrite a VIF port entry. +

+ +
    +
  • +

    +- For each such logical port p whose port security ++ For each such VIF logical port p whose port security + is disabled and 'unknown' address set following flow + is added. +

    +@@ -420,6 +422,22 @@ +
+ + ++
  • ++

    ++ For each such localnet logical port p following flow ++ is added. ++

    ++ ++
      ++
    • ++ Priority 100 flow with the match ++ inport == p and action ++ flags.localnet = 1; ++ reg0[11] = lookup_fdb(inport, eth.src); next; ++
    • ++
    ++
  • ++ +
  • + One priority-0 fallback flow that matches all packets and advances to + the next table. +@@ -429,17 +447,20 @@ +

    Ingress Table 3: Learn MAC of 'unknown' ports.

    + +

    +- This table learns the MAC addresses seen on the logical ports +- whose port security is disabled and 'unknown' address set ++ This table learns the MAC addresses seen on the VIF logical ports ++ whose port security is disabled and 'unknown' address set as well ++ as on localnet ports with localnet_learn_fdb option set + if the lookup_fdb action returned false in the +- previous table. ++ previous table. For localnet ports (with flags.localnet = 1), ++ lookup_fdb returns true if (port, mac) is found or if a mac ++ is found for a port of type vif. +

    + +
      +
    • +

      +- For each such logical port p whose port security +- is disabled and 'unknown' address set following flow ++ For each such VIF logical port p whose port security is ++ disabled and 'unknown' address set and localnet port following flow + is added. +

      + +diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c +index 68fc8836e..b2c5552f6 100644 +--- a/northd/ovn-northd.c ++++ b/northd/ovn-northd.c +@@ -47,7 +47,6 @@ + + VLOG_DEFINE_THIS_MODULE(ovn_northd); + +-static unixctl_cb_func ovn_northd_exit; + static unixctl_cb_func ovn_northd_pause; + static unixctl_cb_func ovn_northd_resume; + static unixctl_cb_func ovn_northd_is_paused; +@@ -752,7 +751,7 @@ main(int argc, char *argv[]) + int res = EXIT_SUCCESS; + struct unixctl_server *unixctl; + int retval; +- bool exiting; ++ struct ovn_exit_args exit_args = {}; + int n_threads = 1; + struct northd_state state = { + .had_lock = false, +@@ -774,7 +773,8 @@ main(int argc, char *argv[]) + if (retval) { + exit(EXIT_FAILURE); + } +- unixctl_command_register("exit", "", 0, 0, ovn_northd_exit, &exiting); ++ unixctl_command_register("exit", "", 0, 0, ovn_exit_command_callback, ++ &exit_args); + unixctl_command_register("pause", "", 0, 0, ovn_northd_pause, &state); + unixctl_command_register("resume", "", 0, 0, ovn_northd_resume, &state); + unixctl_command_register("is-paused", "", 0, 0, ovn_northd_is_paused, +@@ -868,6 +868,8 @@ main(int argc, char *argv[]) + stopwatch_create(LFLOWS_IGMP_STOPWATCH_NAME, SW_MS); + stopwatch_create(LFLOWS_DP_GROUPS_STOPWATCH_NAME, SW_MS); + stopwatch_create(LFLOWS_TO_SB_STOPWATCH_NAME, SW_MS); ++ stopwatch_create(PORT_GROUP_RUN_STOPWATCH_NAME, SW_MS); ++ stopwatch_create(SYNC_METERS_RUN_STOPWATCH_NAME, SW_MS); + + /* Initialize incremental processing engine for ovn-northd */ + inc_proc_northd_init(&ovnnb_idl_loop, &ovnsb_idl_loop); +@@ -878,11 +880,9 @@ main(int argc, char *argv[]) + run_update_worker_pool(n_threads); + + /* Main loop. */ +- exiting = false; +- + struct northd_engine_context eng_ctx = {}; + +- while (!exiting) { ++ while (!exit_args.exiting) { + update_ssl_config(); + memory_run(); + if (memory_should_report()) { +@@ -1024,7 +1024,7 @@ main(int argc, char *argv[]) + unixctl_server_run(unixctl); + unixctl_server_wait(unixctl); + memory_wait(); +- if (exiting) { ++ if (exit_args.exiting) { + poll_immediate_wake(); + } + +@@ -1057,15 +1057,16 @@ main(int argc, char *argv[]) + stopwatch_stop(NORTHD_LOOP_STOPWATCH_NAME, time_msec()); + poll_block(); + if (should_service_stop()) { +- exiting = true; ++ exit_args.exiting = true; + } + stopwatch_start(NORTHD_LOOP_STOPWATCH_NAME, time_msec()); + } + inc_proc_northd_cleanup(); + +- unixctl_server_destroy(unixctl); + ovsdb_idl_loop_destroy(&ovnnb_idl_loop); + ovsdb_idl_loop_destroy(&ovnsb_idl_loop); ++ ovn_exit_args_finish(&exit_args); ++ unixctl_server_destroy(unixctl); + service_stop(); + run_update_worker_pool(0); + ovsrcu_exit(); +@@ -1073,16 +1074,6 @@ main(int argc, char *argv[]) + exit(res); + } + +-static void +-ovn_northd_exit(struct unixctl_conn *conn, int argc OVS_UNUSED, +- const char *argv[] OVS_UNUSED, void *exiting_) +-{ +- bool *exiting = exiting_; +- *exiting = true; +- +- unixctl_command_reply(conn, NULL); +-} +- + static void + ovn_northd_pause(struct unixctl_conn *conn, int argc OVS_UNUSED, + const char *argv[] OVS_UNUSED, void *state_) +diff --git a/ovn-nb.xml b/ovn-nb.xml +index 9131305ea..f84ad90c4 100644 +--- a/ovn-nb.xml ++++ b/ovn-nb.xml +@@ -1101,7 +1101,7 @@ + + + Optional. VLAN EtherType field value for encapsulating VLAN +- headers. Supported values: 802.11q (default), 802.11ad. ++ headers. Supported values: 802.1q (default), 802.1ad. + + + + Looks up A in fdb table. If an entry is found +- and the the logical port key is P, P, ++ and the logical port key is P, P, + stores 1 in the 1-bit subfield +- R, else 0. ++ R, else 0. If flags.localnet is set ++ then 1 is stored if an entry is found and the ++ logical port key is P or if an entry is found and ++ the entry port type is VIF. +

      + +

      +@@ -4888,10 +4891,22 @@ tcp.flags = RST; + + + ++ Deprecated. The group of datapaths to which this load balancer applies ++ to. This means that the same load balancer applies to all datapaths in ++ a group. ++ ++ ++ + The group of datapaths to which this load balancer applies to. This + means that the same load balancer applies to all datapaths in a group. + + ++ ++ The group of logical router datapaths to which this load balancer ++ applies to. This means that the same load balancer applies to all ++ datapaths in a group. ++ ++ + + + IP to be used as source IP for packets that have been hair-pinned after +diff --git a/tests/automake.mk b/tests/automake.mk +index eea0d00f4..f6f0f0e33 100644 +--- a/tests/automake.mk ++++ b/tests/automake.mk +@@ -310,7 +310,8 @@ CHECK_PYFILES = \ + tests/test-l7.py \ + tests/uuidfilt.py \ + tests/test-tcp-rst.py \ +- tests/check_acl_log.py ++ tests/check_acl_log.py \ ++ tests/scapy-server.py + + EXTRA_DIST += $(CHECK_PYFILES) + PYCOV_CLEAN_FILES += $(CHECK_PYFILES:.py=.py,cover) .coverage +diff --git a/tests/checkpatch.at b/tests/checkpatch.at +index 26f319705..e7322fff4 100755 +--- a/tests/checkpatch.at ++++ b/tests/checkpatch.at +@@ -8,7 +8,14 @@ OVS_START_SHELL_HELPERS + try_checkpatch() { + # Take the patch to test from $1. Remove an initial four-space indent + # from it and, if it is just headers with no body, add a null body. ++ # If it does not have a 'Subject', add a valid one. + echo "$1" | sed 's/^ //' > test.patch ++ if grep 'Subject\:' test.patch >/dev/null 2>&1; then : ++ else ++ sed -i'' -e '1i\ ++Subject: Patch this is. ++' test.patch ++ fi + if grep '---' expout >/dev/null 2>&1; then : + else + printf '\n---\n' >> test.patch +@@ -236,6 +243,22 @@ done + AT_CLEANUP + + ++AT_SETUP([checkpatch - catastrophic backtracking]) ++dnl Special case this rather than using the above construct because sometimes a ++dnl warning needs to be generated for line lengths (f.e. when the 'while' ++dnl keyword is used). ++try_checkpatch \ ++ "COMMON_PATCH_HEADER ++ + if (!b_ctx_in->chassis_rec || !b_ctx_in->br_int || !b_ctx_in->ovs_idl_txn) ++ " \ ++ "ERROR: Inappropriate bracing around statement ++ #8 FILE: A.c:1: ++ if (!b_ctx_in->chassis_rec || !b_ctx_in->br_int || !b_ctx_in->ovs_idl_txn) ++" ++ ++AT_CLEANUP ++ ++ + AT_SETUP([checkpatch - parenthesized constructs - for]) + try_checkpatch \ + "COMMON_PATCH_HEADER +@@ -560,3 +583,25 @@ try_checkpatch \ + " + + AT_CLEANUP ++ ++AT_SETUP([checkpatch - subject]) ++try_checkpatch \ ++ "Author: A ++ Commit: A ++ Subject: netdev: invalid case and dot ending ++ ++ Signed-off-by: A" \ ++ "WARNING: The subject summary should start with a capital. ++ WARNING: The subject summary should end with a dot. ++ Subject: netdev: invalid case and dot ending" ++ ++try_checkpatch \ ++ "Author: A ++ Commit: A ++ Subject: netdev: This is a way to long commit summary and therefor it should report a WARNING! ++ ++ Signed-off-by: A" \ ++ "WARNING: The subject, ':

      ', is over 70 characters, i.e., 85. ++ Subject: netdev: This is a way to long commit summary and therefor it should report a WARNING!" ++ ++AT_CLEANUP +diff --git a/tests/ovn-controller-vtep.at b/tests/ovn-controller-vtep.at +index 73971b3f4..50f31b22f 100644 +--- a/tests/ovn-controller-vtep.at ++++ b/tests/ovn-controller-vtep.at +@@ -653,9 +653,6 @@ AT_CHECK([grep -c $northd_version vtep1/ovn-controller-vtep.log], [0], [1 + as northd + OVS_APP_EXIT_AND_WAIT([ovn-northd]) + +-as northd-backup +-OVS_APP_EXIT_AND_WAIT([ovn-northd]) +- + check ovn-sbctl set SB_Global . options:northd_internal_version=foo + check ovn-sbctl set Chassis vtep1 vtep_logical_switches=foo + +diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at +index 4212d601a..6f496d142 100644 +--- a/tests/ovn-controller.at ++++ b/tests/ovn-controller.at +@@ -60,7 +60,6 @@ check_bridge_mappings () { + # the RBAC rules programmed into the SB-DB. The test instruments the SB-DB + # directly and we need to stop northd to avoid overwriting the instrumentation. + kill `cat northd/ovn-northd.pid` +-kill `cat northd-backup/ovn-northd.pid` + kill `cat ovn-nb/ovsdb-server.pid` + + # Initially there should be no patch ports. +@@ -526,6 +525,10 @@ OVS_WAIT_UNTIL([ + test "$(ovn-sbctl get chassis hv1 other_config:port-up-notif)" = '"true"' + ]) + ++OVS_WAIT_UNTIL([ ++ test "$(ovn-sbctl get chassis hv1 other_config:ls-dpg-column)" = '"true"' ++]) ++ + OVN_CLEANUP([hv1]) + AT_CLEANUP + ]) +@@ -575,10 +578,8 @@ localport lport : [[lsp1]] + + # pause ovn-northd + check as northd ovn-appctl -t ovn-northd pause +-check as northd-backup ovn-appctl -t ovn-northd pause + + as northd ovn-appctl -t ovn-northd status +-as northd-backup ovn-appctl -t ovn-northd status + + pb_types=(patch chassisredirect l3gateway localnet localport l2gateway + virtual external remote vtep) +@@ -2093,7 +2094,6 @@ AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], + + # pause ovn-northd + check as northd ovn-appctl -t ovn-northd pause +-check as northd-backup ovn-appctl -t ovn-northd pause + + # Simulate a SB address set "del and add" notification to ovn-controller in the + # same IDL iteration. The flows programmed by ovn-controller should reflect the +@@ -2118,7 +2118,7 @@ AT_CLEANUP + + AT_SETUP([ovn-controller - I-P handle lb_hairpin_use_ct_mark change]) + +-ovn_start --backup-northd=none ++ovn_start + + net_add n1 + sim_add hv1 +@@ -2202,6 +2202,10 @@ OVS_APP_EXIT_AND_WAIT([ovn-controller]) + # The old OVS flows should remain (this is regardless of the configuration) + AT_CHECK([ovs-ofctl dump-flows br-int | grep 10.1.2.3], [0], [ignore]) + ++# We should have 2 flows with groups. ++AT_CHECK([ovs-ofctl dump-flows br-int | grep group -c], [0], [2 ++]) ++ + # Make a change to the ls1-lp1's IP + check ovn-nbctl --wait=sb lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.1.2.4" + +@@ -2214,10 +2218,18 @@ sleep 2 + lflow_run_1=$(ovn-appctl -t ovn-controller coverage/read-counter lflow_run) + AT_CHECK([ovs-ofctl dump-flows br-int | grep 10.1.2.3], [0], [ignore]) + ++# We should have 2 flows with groups. ++AT_CHECK([ovs-ofctl dump-flows br-int | grep group -c], [0], [2 ++]) ++ + sleep 5 + + # Check after the wait + OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 10.1.2.4]) ++ ++# We should have 2 flows with groups. ++AT_CHECK([ovs-ofctl dump-flows br-int | grep group -c], [0], [2 ++]) + lflow_run_2=$(ovn-appctl -t ovn-controller coverage/read-counter lflow_run) + + # Verify that the flow compute completed during the wait (after the wait it +@@ -2230,7 +2242,7 @@ OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) + start_daemon ovs-vswitchd --enable-dummy=system -vvconn -vofproto_dpif -vunixctl + OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 10.1.2.4]) + +-check ovn-nbctl --wait=hv lb-add lb3 2.2.2.2 10.1.2.5 \ ++check ovn-nbctl --wait=hv lb-add lb3 3.3.3.3 10.1.2.5 \ + -- ls-lb-add ls1 lb3 + + # There should be 3 group IDs allocated (this is to ensure the group ID +@@ -2238,6 +2250,10 @@ check ovn-nbctl --wait=hv lb-add lb3 2.2.2.2 10.1.2.5 \ + AT_CHECK([ovn-appctl -t ovn-controller group-table-list | awk '{print $2}' | sort | uniq | wc -l], [0], [3 + ]) + ++# We should have 3 flows with groups. ++AT_CHECK([ovs-ofctl dump-flows br-int | grep group -c], [0], [3 ++]) ++ + OVN_CLEANUP([hv1]) + AT_CLEANUP + +diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at +index d265bb90b..d4c436f84 100644 +--- a/tests/ovn-ic.at ++++ b/tests/ovn-ic.at +@@ -92,6 +92,7 @@ check ovn-nbctl lr-add lr1 + check ovn-nbctl lrp-add lr1 lrp1 00:00:00:00:00:01 10.0.0.1/24 + check ovn-nbctl lrp-set-gateway-chassis lrp1 gw-az1 + ++OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1]) + check ovn-nbctl lsp-add ts1 lsp1 -- \ + lsp-set-addresses lsp1 router -- \ + lsp-set-type lsp1 router -- \ +@@ -156,6 +157,7 @@ create_ic_infra() { + ovn_as $az + + check ovn-ic-nbctl ts-add $ts ++ OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts]) + check ovn-nbctl lr-add $lr + check ovn-nbctl lrp-add $lr $lrp 00:00:00:00:00:0$az_id 10.0.$az_id.1/24 + check ovn-nbctl lrp-set-gateway-chassis $lrp gw-$az +@@ -227,6 +229,7 @@ for i in 1 2; do + check ovn-nbctl lrp-add lr1 lrp$i 00:00:00:00:0$i:01 10.0.$i.1/24 + check ovn-nbctl lrp-set-gateway-chassis lrp$i gw-az$i + ++ OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1]) + check ovn-nbctl lsp-add ts1 lsp$i -- \ + lsp-set-addresses lsp$i router -- \ + lsp-set-type lsp$i router -- \ +@@ -290,7 +293,7 @@ ovs-vsctl set open . external-ids:ovn-is-interconn=true + OVS_WAIT_UNTIL([ovn_as az2 ovn-sbctl show | grep gw1]) + + OVN_CLEANUP_SBOX(gw1) +-AT_CHECK([ovn_as az2 ovn-sbctl show], [0], [dnl ++OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-sbctl show], [0], [dnl + ]) + + # Test encap change +@@ -335,16 +338,17 @@ ovn-nbctl lsp-add ts1 lsp-ts1-lr1 + ovn-nbctl lsp-set-addresses lsp-ts1-lr1 router + ovn-nbctl lsp-set-type lsp-ts1-lr1 router + ovn-nbctl --wait=hv lsp-set-options lsp-ts1-lr1 router-port=lrp-lr1-ts1 +- ++OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl show | grep lsp-ts1-lr1]) + ovn_as az2 ovn-nbctl lsp-set-options lsp-ts1-lr1 requested-chassis=gw1 +-AT_CHECK([ovn_as az2 ovn-nbctl show | uuidfilt], [0], [dnl ++ ++OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-nbctl show | uuidfilt], [0], [dnl + switch <0> (ts1) + port lsp-ts1-lr1 + type: remote + addresses: [["aa:aa:aa:aa:aa:01 169.254.100.1/24"]] + ]) + +-AT_CHECK([ovn_as az2 ovn-sbctl -f csv -d bare --no-headings --columns logical_port,type list port_binding], [0], [dnl ++OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-sbctl -f csv -d bare --no-headings --columns logical_port,type list port_binding], [0], [dnl + lsp-ts1-lr1,remote + ]) + +@@ -529,6 +533,7 @@ for i in 1 2; do + for j in 1 2; do + ts=ts$j$j + ovn-ic-nbctl --may-exist ts-add $ts ++ OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts]) + + # Create LRP and connect to TS + lr=lr$j$i +@@ -552,6 +557,9 @@ ovn_as az1 ovn-nbctl show + echo az2 + ovn_as az2 ovn-nbctl show + ++OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep learned | grep 192.168]) ++OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep learned | grep 10.10.10]) ++ + # Test routes from lr12 were learned to lr11 + AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | + grep learned | awk '{print $1, $2}' | sort], [0], [dnl +@@ -584,6 +592,7 @@ for i in 1 2; do + # Create LRP and connect to TS + ovn-nbctl lr-add lr$i + ovn-nbctl lrp-add lr$i lrp-lr$i-ts1 aa:aa:aa:aa:aa:0$i 169.254.100.$i/24 ++ OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1]) + ovn-nbctl lsp-add ts1 lsp-ts1-lr$i \ + -- lsp-set-addresses lsp-ts1-lr$i router \ + -- lsp-set-type lsp-ts1-lr$i router \ +@@ -909,6 +918,7 @@ for i in 1 2; do + for j in 1 2 3; do + ts=ts1$j + ovn-ic-nbctl --may-exist ts-add $ts ++ OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts]) + + lrp=lrp-$lr-$ts + lsp=lsp-$ts-$lr +@@ -934,6 +944,7 @@ for i in 1 2; do + for j in 1 2; do + ts=ts2$j + ovn-ic-nbctl --may-exist ts-add $ts ++ OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts]) + + lrp=lrp-$lr-$ts + lsp=lsp-$ts-$lr +@@ -957,7 +968,7 @@ ovn_as az2 ovn-nbctl --route-table=rtb3 lr-route-add lr12 10.10.10.0/24 192.168. + ovn_as az2 ovn-nbctl --wait=sb lrp-add lr22 lrp-lr22 aa:aa:aa:aa:bb:01 "192.168.0.1/24" + + # Test direct routes from lr12 were learned to lr11 +-AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 | ++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 | + grep learned | awk '{print $1, $2, $5}' | sort ], [0], [dnl + 192.168.0.0/24 169.254.101.2 ecmp + 192.168.0.0/24 169.254.102.2 ecmp +@@ -965,24 +976,24 @@ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 | + ]) + + # Test static routes from lr12 rtbs rtb1,rtb2,rtb3 were learned to lr11 +-AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-list lr11], [0], [dnl ++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-list lr11], [0], [dnl + IPv4 Routes + Route Table rtb1: + 10.10.10.0/24 169.254.101.2 dst-ip (learned) + ]) +-AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb2 lr-route-list lr11], [0], [dnl ++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl --route-table=rtb2 lr-route-list lr11], [0], [dnl + IPv4 Routes + Route Table rtb2: + 10.10.10.0/24 169.254.102.2 dst-ip (learned) + ]) +-AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb3 lr-route-list lr11], [0], [dnl ++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl --route-table=rtb3 lr-route-list lr11], [0], [dnl + IPv4 Routes + Route Table rtb3: + 10.10.10.0/24 169.254.103.2 dst-ip (learned) + ]) + + # Test routes from lr12 didn't leak as learned to lr21 +-AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr21 | grep 192.168 | sort], [0], [dnl ++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr21 | grep 192.168 | sort], [0], [dnl + 192.168.0.0/24 169.254.101.2 dst-ip (learned) ecmp + 192.168.0.0/24 169.254.102.2 dst-ip (learned) ecmp + ]) +@@ -1035,6 +1046,7 @@ for i in 1 2; do + for j in 1 2 3; do + ts=ts1$j + ovn-ic-nbctl --may-exist ts-add $ts ++ OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts]) + + lrp=lrp-$lr-$ts + lsp=lsp-$ts-$lr +@@ -1060,6 +1072,7 @@ for i in 1 2; do + for j in 1 2; do + ts=ts2$j + ovn-ic-nbctl --may-exist ts-add $ts ++ OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts]) + + lrp=lrp-$lr-$ts + lsp=lsp-$ts-$lr +@@ -1083,6 +1096,7 @@ ovn_as az2 ovn-nbctl --route-table=rtb3 lr-route-add lr12 2001:db8:aaaa::/64 200 + ovn_as az2 ovn-nbctl --wait=sb lrp-add lr22 lrp-lr22 aa:aa:aa:aa:bb:01 "2001:db8:200::1/64" + + # Test direct routes from lr12 were learned to lr11 ++OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 2001:db8:3::2]) + AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 2001:db8:200 | + grep learned | awk '{print $1, $2, $5}' | sort], [0], [dnl + 2001:db8:200::/64 2001:db8:1::2 ecmp +@@ -1091,16 +1105,19 @@ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 2001:db8:200 | + ]) + + # Test static routes from lr12 rtbs rtb1,rtb2,rtb3 were learned to lr11 ++OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-list lr11 | grep learned]) + AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-list lr11], [0], [dnl + IPv6 Routes + Route Table rtb1: + 2001:db8:aaaa::/64 2001:db8:1::2 dst-ip (learned) + ]) ++OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl --route-table=rtb2 lr-route-list lr11 | grep learned]) + AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb2 lr-route-list lr11], [0], [dnl + IPv6 Routes + Route Table rtb2: + 2001:db8:aaaa::/64 2001:db8:2::2 dst-ip (learned) + ]) ++OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl --route-table=rtb3 lr-route-list lr11 | grep learned]) + AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb3 lr-route-list lr11], [0], [dnl + IPv6 Routes + Route Table rtb3: +@@ -1108,6 +1125,7 @@ Route Table rtb3: + ]) + + # Test routes from lr12 didn't leak as learned to lr21 ++OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr21 | grep "2001:db8:2::2" | grep learned]) + AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr21 | grep 2001 | sort], [0], [dnl + 2001:db8:200::/64 2001:db8:1::2 dst-ip (learned) ecmp + 2001:db8:200::/64 2001:db8:2::2 dst-ip (learned) ecmp +@@ -1127,6 +1145,7 @@ ovn-ic-nbctl ts-add ts1 + for i in 1 2; do + ovn_start az$i + ovn_as az$i ++ OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1]) + + # Enable route learning at AZ level + ovn-nbctl set nb_global . options:ic-route-learn=true +@@ -1152,13 +1171,13 @@ for i in 1 2; do + check ovn-nbctl --wait=sb lr-route-add $lr 0.0.0.0/0 192.168.$i.11 + done + +-AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep dst-ip | sort], [0], [dnl ++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep dst-ip | sort] , [0], [dnl + 0.0.0.0/0 192.168.1.11 dst-ip + 10.0.0.0/24 192.168.1.10 dst-ip + 192.168.2.0/24 169.254.100.2 dst-ip (learned) + ]) + +-AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr12 | grep dst-ip | sort], [0], [dnl ++OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-nbctl lr-route-list lr12 | grep dst-ip | sort], [0], [dnl + 0.0.0.0/0 192.168.2.11 dst-ip + 10.0.0.0/24 192.168.2.10 dst-ip + 192.168.1.0/24 169.254.100.1 dst-ip (learned) +@@ -1191,10 +1210,10 @@ done + # each LR has one connected subnet except TS port + + +-# create lr11, lr21, lr22, ts1 and connect them +-ovn-ic-nbctl ts-add ts1 ++# create lr11, lr21, lr22 and connect them + + ovn_as az1 ++OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1]) + + lr=lr11 + ovn-nbctl lr-add $lr +@@ -1209,6 +1228,7 @@ ovn-nbctl lsp-add ts1 $lsp \ + -- lsp-set-options $lsp router-port=$lrp + + ovn_as az2 ++OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1]) + for i in 1 2; do + lr=lr2$i + ovn-nbctl lr-add $lr +@@ -1230,7 +1250,7 @@ ovn_as az2 ovn-nbctl lrp-add lr21 lrp-lr21 aa:aa:aa:aa:bc:01 "192.168.1.1/24" + ovn_as az2 ovn-nbctl lrp-add lr22 lrp-lr22 aa:aa:aa:aa:bc:02 "192.168.2.1/24" + + # Test direct routes from lr21 and lr22 were learned to lr11 +-AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 | ++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 | + grep learned | awk '{print $1, $2}' | sort ], [0], [dnl + 192.168.1.0/24 169.254.10.21 + 192.168.2.0/24 169.254.10.22 +diff --git a/tests/ovn-ipsec.at b/tests/ovn-ipsec.at +index 10ef97878..f8df8d60e 100644 +--- a/tests/ovn-ipsec.at ++++ b/tests/ovn-ipsec.at +@@ -48,11 +48,11 @@ ovn-nbctl set nb_global . options:ipsec_encapsulation=true + + check ovn-nbctl --wait=hv sync + +-AT_CHECK([as hv2 ovs-vsctl get Interface ovn-hv1-0 options:remote_ip | tr -d '"\n'], [0], [192.168.0.1]) ++OVS_WAIT_UNTIL([test x`as hv2 ovs-vsctl get Interface ovn-hv1-0 options:remote_ip | tr -d '"\n'` = x192.168.0.1]) + AT_CHECK([as hv2 ovs-vsctl get Interface ovn-hv1-0 options:local_ip | tr -d '"\n'], [0], [192.168.0.2]) + AT_CHECK([as hv2 ovs-vsctl get Interface ovn-hv1-0 options:remote_name | tr -d '\n'], [0], [hv1]) + AT_CHECK([as hv2 ovs-vsctl get Interface ovn-hv1-0 options:ipsec_encapsulation | tr -d '\n'], [0], [yes]) +-AT_CHECK([as hv1 ovs-vsctl get Interface ovn-hv2-0 options:remote_ip | tr -d '"\n'], [0], [192.168.0.2]) ++OVS_WAIT_UNTIL([test x`as hv1 ovs-vsctl get Interface ovn-hv2-0 options:remote_ip | tr -d '"\n'` = x192.168.0.2]) + AT_CHECK([as hv1 ovs-vsctl get Interface ovn-hv2-0 options:local_ip | tr -d '"\n'], [0], [192.168.0.1]) + AT_CHECK([as hv1 ovs-vsctl get Interface ovn-hv2-0 options:remote_name | tr -d '\n'], [0], [hv2]) + AT_CHECK([as hv1 ovs-vsctl get Interface ovn-hv2-0 options:ipsec_encapsulation | tr -d '\n'], [0], [yes]) +diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at +index 13d5dc3d4..bb93fa5f0 100644 +--- a/tests/ovn-macros.at ++++ b/tests/ovn-macros.at +@@ -188,16 +188,16 @@ ovn_start_northd() { + # ovn-sbctl and ovn-nbctl use them by default, and starts ovn-northd running + # against them. + # +-# Normally this starts an active northd and a backup northd. The following ++# Normally this starts only an active northd and no backup northd. The following + # options are accepted to adjust that: +-# --backup-northd=none Don't start a backup northd. ++# --backup-northd Start a backup northd. + # --backup-northd=paused Start the backup northd in the paused state. + ovn_start () { +- local backup_northd=: ++ local backup_northd=false + local backup_northd_options= + case $1 in +- --backup-northd=none) backup_northd=false; shift ;; +- --backup-northd=paused) backup_northd_options=--paused; shift ;; ++ --backup-northd) backup_northd=true; shift ;; ++ --backup-northd=paused) backup_northd=true; backup_northd_options=--paused; shift ;; + esac + local AZ=$1 + local msg_prefix=${AZ:+$AZ: } +@@ -288,11 +288,44 @@ net_attach () { + || return 1 + } + ++ovn_wait_for_encaps() { ++ local systemid=$1 ++ ++ if [[ -f "${OVN_SYSCONFDIR}/system-id-override" ]]; then ++ systemid=$(cat ${OVN_SYSCONFDIR}/system-id-override) ++ fi ++ ++ local encap=$(ovs-vsctl get Open_vSwitch . external_ids:ovn-encap-type-$systemid) ++ if [[ -z "$encap" ]]; then ++ encap=$(ovs-vsctl get Open_vSwitch . external_ids:ovn-encap-type) ++ fi ++ encap=$(tr -d '"' <<< $encap) ++ ++ local ip=$(ovs-vsctl get Open_vSwitch . external_ids:ovn-encap-ip-$systemid) ++ if [[ -z "$ip" ]]; then ++ ip=$(ovs-vsctl get Open_vSwitch . external_ids:ovn-encap-ip) ++ fi ++ ip=$(tr -d '"' <<< $ip) ++ ++ IFS="," read -r -a encap_types <<< "$encap" ++ for e in "${encap_types[[@]]}"; do ++ wait_column "$ip" sb:Encap ip chassis_name="$systemid" type="$e" ++ done ++} ++ + # ovn_az_attach AZ NETWORK BRIDGE IP [MASKLEN] [ENCAP] + ovn_az_attach() { +- local az=$1 net=$2 bridge=$3 ip=$4 masklen=${5-24} encap=${6-geneve,vxlan} systemid=${7-$sandbox} cli_args=${@:8} ++ local az=$1 net=$2 bridge=$3 ip=$4 masklen=${5-24} encap=${6-geneve,vxlan} ++ local systemid=${7-$sandbox} systemid_override=$8 + net_attach $net $bridge || return 1 + ++ local expected_encap_id=$systemid ++ local cli_args="" ++ if [[ -n "$systemid_override" ]]; then ++ cli_args="-n $systemid_override" ++ expected_encap_id=$systemid_override ++ fi ++ + mac=`ovs-vsctl get Interface $bridge mac_in_use | sed s/\"//g` + arp_table="$arp_table $sandbox,$bridge,$ip,$mac" + if test -z $(echo $ip | sed '/:/d'); then +@@ -332,6 +365,11 @@ ovn_az_attach() { + fi + + start_daemon ovn-controller --enable-dummy-vif-plug ${cli_args} || return 1 ++ if test X"$az" = XNONE; then ++ ovn_wait_for_encaps $expected_encap_id ++ else ++ ovn_as $az ovn_wait_for_encaps $expected_encap_id ++ fi + } + + # ovn_attach NETWORK BRIDGE IP [MASKLEN] [ENCAP] +@@ -372,6 +410,7 @@ start_virtual_controller() { + || return 1 + + ovn-controller --enable-dummy-vif-plug ${cli_args} -vconsole:off --detach --no-chdir ++ ovn_wait_for_encaps $systemid + } + + # ovn_setenv AZ +@@ -833,15 +872,40 @@ ovn_trace_client() { + # ovs-appctl netdev-dummy/receive $vif $packet + # + fmt_pkt() { +- echo "from scapy.all import *; \ +- import binascii; \ +- out = binascii.hexlify(raw($1)); \ +- print(out.decode())" | $PYTHON3 ++ ctlfile=$ovs_base/scapy.ctl ++ if [[ ! -S $ctlfile ]]; then ++ start_scapy_server ++ fi ++ while [[ ! -S $ctlfile ]]; do sleep 0.1; done ++ ovs-appctl -t $ctlfile payload "$1" ++} ++ ++start_scapy_server() { ++ pidfile=$ovs_base/scapy.pid ++ ctlfile=$ovs_base/scapy.ctl ++ logfile=$ovs_base/scapy.log ++ lockfile=$ovs_base/scapy.lock ++ ++ flock -n $lockfile "$top_srcdir"/tests/scapy-server.py \ ++ --pidfile=$pidfile --unixctl=$ctlfile --log-file=$logfile --detach \ ++ && on_exit "test -e \"$pidfile\" && ovs-appctl -t $ctlfile exit" ++} ++ ++sleep_northd() { ++ echo Northd going to sleep ++ AT_CHECK([kill -STOP $(cat northd/ovn-northd.pid)]) ++ on_exit "kill -CONT $(cat northd/ovn-northd.pid)" ++} ++ ++wake_up_northd() { ++ echo Northd waking up ++ AT_CHECK([kill -CONT $(cat northd/ovn-northd.pid)]) + } + + sleep_sb() { + echo SB going to sleep + AT_CHECK([kill -STOP $(cat ovn-sb/ovsdb-server.pid)]) ++ on_exit "kill -CONT $(cat ovn-sb/ovsdb-server.pid)" + } + wake_up_sb() { + echo SB waking up +@@ -865,6 +929,7 @@ sleep_ovs() { + hv=$1 + echo ovs $hv going to sleep + AT_CHECK([kill -STOP $(cat $hv/ovs-vswitchd.pid)]) ++ on_exit "kill -CONT $(cat $hv/ovs-vswitchd.pid)" + } + + wake_up_ovs() { +@@ -876,12 +941,17 @@ wake_up_ovs() { + sleep_ovsdb() { + echo OVSDB $1 going to sleep + AT_CHECK([kill -STOP $(cat $1/ovsdb-server.pid)]) ++ on_exit "kill -CONT $(cat $1/ovsdb-server.pid)" + } + wake_up_ovsdb() { + echo OVSDB $1 waking up + AT_CHECK([kill -CONT $(cat $1/ovsdb-server.pid)]) + } + ++trim_zeros() { ++ sed 's/\(00\)\{1,\}$//' ++} ++ + OVS_END_SHELL_HELPERS + + m4_define([OVN_POPULATE_ARP], [AT_CHECK(ovn_populate_arp__, [0], [ignore])]) +diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at +index fde3a28ee..94641f2f0 100644 +--- a/tests/ovn-nbctl.at ++++ b/tests/ovn-nbctl.at +@@ -2695,7 +2695,9 @@ dnl --------------------------------------------------------------------- + + AT_SETUP([ovn-nbctl - daemon retry connection]) + OVN_NBCTL_TEST_START daemon +-AT_CHECK([kill `cat ovsdb-server.pid`]) ++pid=$(cat ovsdb-server.pid) ++AT_CHECK([kill $pid]) ++OVS_WAIT_WHILE([kill -0 $pid 2>/dev/null]) + AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --log-file --remote=punix:$OVS_RUNDIR/ovnnb_db.sock ovn-nb.db], [0], [], [stderr]) + AT_CHECK([ovn-nbctl show], [0], [ignore]) + OVN_NBCTL_TEST_STOP "/terminating with signal 15/d" +diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at +index 65d3f4b03..c32122025 100644 +--- a/tests/ovn-northd.at ++++ b/tests/ovn-northd.at +@@ -20,6 +20,14 @@ m4_define([_DUMP_DB_TABLES], [ + # sure nothing is changed by the recompute. It is used for ensuring the + # correctness of incremental processing. + m4_define([CHECK_NO_CHANGE_AFTER_RECOMPUTE], [ ++ wait_port_up=$1 ++ if test X$wait_port_up = X1; then ++ # Wait for hv to have received all previous commands ++ # Make sure ports are up as otherwise it might come as a difference after recompute ++ echo "waiting for ports up" ++ check ovn-nbctl --wait=hv sync ++ wait_for_ports_up ++ fi + _DUMP_DB_TABLES(before) + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/recompute + check ovn-nbctl --wait=sb sync +@@ -813,7 +821,7 @@ AT_CLEANUP + + OVN_FOR_EACH_NORTHD_NO_HV([ + AT_SETUP([ovn-northd restart]) +-ovn_start --backup-northd=none ++ovn_start + + # Check that ovn-northd is active, by verifying that it creates and + # destroys southbound datapaths as one would expect. +@@ -832,7 +840,7 @@ sleep 5 + check_row_count Datapath_Binding 1 + + # Now resume ovn-northd. Changes should catch up. +-ovn_start_northd primary ++ovn_start_northd + wait_row_count Datapath_Binding 2 + + AT_CLEANUP +@@ -841,7 +849,7 @@ AT_CLEANUP + OVN_FOR_EACH_NORTHD_NO_HV([ + AT_SETUP([northbound database reconnection]) + +-ovn_start --backup-northd=none ++ovn_start + + # Check that ovn-northd is active, by verifying that it creates and + # destroys southbound datapaths as one would expect. +@@ -873,7 +881,7 @@ AT_CLEANUP + OVN_FOR_EACH_NORTHD_NO_HV([ + AT_SETUP([southbound database reconnection]) + +-ovn_start --backup-northd=none ++ovn_start + + # Check that ovn-northd is active, by verifying that it creates and + # destroys southbound datapaths as one would expect. +@@ -1100,7 +1108,7 @@ AT_CAPTURE_FILE([crflows]) + AT_CHECK([grep -e "lr_out_snat" drflows | sed 's/table=../table=??/' | sort], [0], [dnl + table=??(lr_out_snat ), priority=0 , match=(1), action=(next;) + table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) +- table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range), action=(ct_snat(172.16.1.1);) ++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);) + ]) + + AT_CHECK([grep -e "lr_out_snat" crflows | sed 's/table=../table=??/' | sort], [0], [dnl +@@ -1130,7 +1138,7 @@ AT_CAPTURE_FILE([crflows2]) + AT_CHECK([grep -e "lr_out_snat" drflows2 | sed 's/table=../table=??/' | sort], [0], [dnl + table=??(lr_out_snat ), priority=0 , match=(1), action=(next;) + table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) +- table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat(172.16.1.1);) ++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);) + table=??(lr_out_snat ), priority=163 , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $disallowed_range), action=(next;) + ]) + +@@ -1159,7 +1167,7 @@ AT_CAPTURE_FILE([crflows2]) + AT_CHECK([grep -e "lr_out_snat" drflows3 | sed 's/table=../table=??/' | sort], [0], [dnl + table=??(lr_out_snat ), priority=0 , match=(1), action=(next;) + table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) +- table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range), action=(ct_snat(172.16.1.2);) ++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.2);) + ]) + + AT_CHECK([grep -e "lr_out_snat" crflows3 | sed 's/table=../table=??/' | sort], [0], [dnl +@@ -1186,7 +1194,7 @@ AT_CAPTURE_FILE([crflows2]) + AT_CHECK([grep -e "lr_out_snat" drflows4 | sed 's/table=../table=??/' | sort], [0], [dnl + table=??(lr_out_snat ), priority=0 , match=(1), action=(next;) + table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) +- table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat(172.16.1.2);) ++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.2);) + table=??(lr_out_snat ), priority=163 , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $disallowed_range), action=(next;) + ]) + +@@ -2860,7 +2868,7 @@ lbg0_uuid=$(fetch_column sb:load_balancer _uuid name=lbg0) + echo + echo "__file__:__line__: Check that SB lb0 has sw0 in datapaths column." + +-lb0_dp_group=$(fetch_column sb:load_balancer datapath_group name=lb0) ++lb0_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lb0) + AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl + | grep -A1 $lb0_dp_group | tail -1], [0], [dnl + $sw0_sb_uuid +@@ -2871,7 +2879,7 @@ check_column "" sb:datapath_binding load_balancers external_ids:name=sw0 + echo + echo "__file__:__line__: Check that SB lbg0 has sw0 in datapaths column." + +-lbg0_dp_group=$(fetch_column sb:load_balancer datapath_group name=lbg0) ++lbg0_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lbg0) + AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl + | grep -A1 $lbg0_dp_group | tail -1], [0], [dnl + $sw0_sb_uuid +@@ -2897,6 +2905,15 @@ sb:load_balancer vips,protocol name=lbg0 + + check ovn-nbctl lr-add lr0 -- add logical_router lr0 load_balancer_group $lbg + check ovn-nbctl --wait=sb lr-lb-add lr0 lb0 ++check_row_count sb:load_balancer 2 ++ ++lr0_sb_uuid=$(fetch_column datapath_binding _uuid external_ids:name=lr0) ++lb0_lr_dp_group=$(fetch_column sb:load_balancer lr_datapath_group name=lb0) ++ ++AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl ++ | grep -A1 $lb0_lr_dp_group | tail -1], [0], [dnl ++$lr0_sb_uuid ++]) + + echo + echo "__file__:__line__: Check that SB lb0 has only sw0 in datapaths column." +@@ -2942,7 +2959,13 @@ check_row_count sb:load_balancer 2 + lbg1=$(fetch_column nb:load_balancer _uuid name=lbg1) + check ovn-nbctl add load_balancer_group $lbg load_balancer $lbg1 + check ovn-nbctl --wait=sb lr-lb-add lr0 lb1 +-check_row_count sb:load_balancer 3 ++check_row_count sb:load_balancer 4 ++ ++lb1_lr_dp_group=$(fetch_column sb:load_balancer lr_datapath_group name=lb1) ++AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl ++ | grep -A1 $lb1_lr_dp_group | tail -1], [0], [dnl ++$lr0_sb_uuid ++]) + + echo + echo "__file__:__line__: Associate lb1 to sw1 and check that lb1 is created in SB DB." +@@ -2959,7 +2982,7 @@ echo "__file__:__line__: Check that SB lbg1 has vips and protocol columns are se + check_column "20.0.0.30:80=20.0.0.50:8080 udp" sb:load_balancer vips,protocol name=lbg1 + + lb1_uuid=$(fetch_column sb:load_balancer _uuid name=lb1) +-lb1_dp_group=$(fetch_column sb:load_balancer datapath_group name=lb1) ++lb1_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lb1) + + echo + echo "__file__:__line__: Check that SB lb1 has sw1 in datapaths column." +@@ -2970,7 +2993,7 @@ $sw1_sb_uuid + ]) + + lbg1_uuid=$(fetch_column sb:load_balancer _uuid name=lbg1) +-lbg1_dp_group=$(fetch_column sb:load_balancer datapath_group name=lbg1) ++lbg1_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lbg1) + + echo + echo "__file__:__line__: Check that SB lbg1 has sw0 and sw1 in datapaths column." +@@ -4596,7 +4619,7 @@ AT_SKIP_IF([test "$HAVE_OPENSSL" = no]) + PKIDIR="$(cd $abs_top_builddir/tests && pwd)" + AT_SKIP_IF([expr "$PKIDIR" : ".*[[ '\" + \\]]"]) +-ovn_start --backup-northd=none ++ovn_start + + as northd + OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) +@@ -5095,6 +5118,7 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort], + table=??(ls_in_l2_lkup ), priority=70 , match=(eth.mcast), action=(outport = "_MC_flood"; output;) + table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:01:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) ++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + ]) +@@ -5109,6 +5133,7 @@ AT_CHECK([grep "ls_in_l2_lkup" ls2_lflows | sed 's/table=../table=??/' | sort], + table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:02:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.2.1), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.100), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;) ++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.200), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:201), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;) + ]) + +@@ -5129,8 +5154,10 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort], + table=??(ls_in_l2_lkup ), priority=70 , match=(eth.mcast), action=(outport = "_MC_flood"; output;) + table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:01:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) ++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) ++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + ]) + +@@ -5144,7 +5171,9 @@ AT_CHECK([grep "ls_in_l2_lkup" ls2_lflows | sed 's/table=../table=??/' | sort], + table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:02:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.2.1), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.100), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;) ++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.200), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 40.0.0.100), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;) ++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 40.0.0.200), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:201), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;) + ]) + +@@ -5162,9 +5191,11 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort], + table=??(ls_in_l2_lkup ), priority=70 , match=(eth.mcast), action=(outport = "_MC_flood"; output;) + table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:01:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) ++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) ++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + ]) + +@@ -5180,9 +5211,11 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort], + table=??(ls_in_l2_lkup ), priority=70 , match=(eth.mcast), action=(outport = "_MC_flood"; output;) + table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:01:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) ++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) ++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + ]) + +@@ -5204,9 +5237,11 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort], + table=??(ls_in_l2_lkup ), priority=70 , match=(eth.mcast), action=(outport = "_MC_flood"; output;) + table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:01:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) ++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) ++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;) + ]) + +@@ -5364,12 +5399,12 @@ AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], + AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl + table=? (lr_out_snat ), priority=0 , match=(1), action=(next;) + table=? (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) +- table=? (lr_out_snat ), priority=153 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.10);) +- table=? (lr_out_snat ), priority=154 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.10);) +- table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.30);) +- table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.20);) +- table=? (lr_out_snat ), priority=162 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.30);) +- table=? (lr_out_snat ), priority=162 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.20);) ++ table=? (lr_out_snat ), priority=153 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.10);) ++ table=? (lr_out_snat ), priority=154 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.10);) ++ table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.30);) ++ table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.20);) ++ table=? (lr_out_snat ), priority=162 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.30);) ++ table=? (lr_out_snat ), priority=162 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.20);) + ]) + + # Separate zones for DGP +@@ -5412,9 +5447,9 @@ AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], + AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl + table=? (lr_out_snat ), priority=0 , match=(1), action=(next;) + table=? (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) +- table=? (lr_out_snat ), priority=153 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.10);) +- table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.30);) +- table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.20);) ++ table=? (lr_out_snat ), priority=153 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);) ++ table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);) ++ table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);) + ]) + + # Associate load balancer to lr0 +@@ -5494,12 +5529,12 @@ AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], + AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl + table=? (lr_out_snat ), priority=0 , match=(1), action=(next;) + table=? (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) +- table=? (lr_out_snat ), priority=153 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.10);) +- table=? (lr_out_snat ), priority=154 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.10);) +- table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.30);) +- table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.20);) +- table=? (lr_out_snat ), priority=162 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.30);) +- table=? (lr_out_snat ), priority=162 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.20);) ++ table=? (lr_out_snat ), priority=153 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.10);) ++ table=? (lr_out_snat ), priority=154 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.10);) ++ table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.30);) ++ table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.20);) ++ table=? (lr_out_snat ), priority=162 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.30);) ++ table=? (lr_out_snat ), priority=162 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.20);) + ]) + + # Separate zones for DGP +@@ -5560,9 +5595,9 @@ AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], + AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl + table=? (lr_out_snat ), priority=0 , match=(1), action=(next;) + table=? (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) +- table=? (lr_out_snat ), priority=153 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.10);) +- table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.30);) +- table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.20);) ++ table=? (lr_out_snat ), priority=153 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);) ++ table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);) ++ table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);) + ]) + + # Make the logical router as Gateway router +@@ -6019,9 +6054,9 @@ ovn_start + check ovn-nbctl ls-add ls -- lb-add lb1 10.0.0.1:80 10.0.0.2:80 -- ls-lb-add ls lb1 + check ovn-nbctl --wait=sb sync + +-dps=$(fetch_column Load_Balancer datapath_group) ++dps=$(fetch_column Load_Balancer ls_datapath_group) + nlb=$(fetch_column nb:Load_Balancer _uuid) +-AT_CHECK([ovn-sbctl create Load_Balancer name=lb1 datapath_group="$dps" external_ids="lb_id=$nlb"], [0], [ignore]) ++AT_CHECK([ovn-sbctl create Load_Balancer name=lb1 ls_datapath_group="$dps" external_ids="lb_id=$nlb"], [0], [ignore]) + + check ovn-nbctl --wait=sb sync + check_row_count Load_Balancer 1 +@@ -6105,10 +6140,10 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=. + table=??(lr_in_chk_pkt_len ), priority=0 , match=(1), action=(next;) + table=??(lr_in_chk_pkt_len ), priority=50 , match=(outport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); next;) + table=??(lr_in_larger_pkts ), priority=0 , match=(1), action=(next;) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) + ]) + + AT_CHECK([grep -E "lr_in_admission.*check_pkt_larger" lr0flows | sort], [0], [dnl +@@ -6136,10 +6171,10 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=. + table=??(lr_in_chk_pkt_len ), priority=0 , match=(1), action=(next;) + table=??(lr_in_chk_pkt_len ), priority=50 , match=(outport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); next;) + table=??(lr_in_larger_pkts ), priority=0 , match=(1), action=(next;) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) + ]) + + AT_CHECK([grep -E "lr_in_admission.*check_pkt_larger" lr0flows | sort], [0], [dnl +@@ -6165,10 +6200,10 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=. + table=??(lr_in_chk_pkt_len ), priority=50 , match=(outport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); next;) + table=??(lr_in_chk_pkt_len ), priority=55 , match=(outport == "lr0-public" && (tcp)), action=(next;) + table=??(lr_in_larger_pkts ), priority=0 , match=(1), action=(next;) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) + ]) + + AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" -e "tcp" | sort], [0], [dnl +@@ -6190,14 +6225,14 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=. + table=??(lr_in_chk_pkt_len ), priority=50 , match=(outport == "lr0-sw0"), action=(reg9[[1]] = check_pkt_larger(1414); next;) + table=??(lr_in_chk_pkt_len ), priority=55 , match=(outport == "lr0-public" && (tcp)), action=(next;) + table=??(lr_in_larger_pkts ), priority=0 , match=(1), action=(next;) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };) + ]) + + AT_CHECK([grep "lr_in_admission.*check_pkt_larger" lr0flows | sort], [0], [dnl +@@ -6229,14 +6264,14 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=. + table=??(lr_in_chk_pkt_len ), priority=55 , match=(outport == "lr0-public" && (tcp)), action=(next;) + table=??(lr_in_chk_pkt_len ), priority=55 , match=(outport == "lr0-sw0" && (tcp)), action=(next;) + table=??(lr_in_larger_pkts ), priority=0 , match=(1), action=(next;) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };) + ]) + + AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" -e "tcp" | sort], [0], [dnl +@@ -6255,15 +6290,16 @@ check ovn-nbctl --wait=sb clear logical_router_port lr0-public options + ovn-sbctl dump-flows lr0 > lr0flows + AT_CAPTURE_FILE([lr0flows]) + ++grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=../table=??/' | sort + AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl + table=??(lr_in_chk_pkt_len ), priority=0 , match=(1), action=(next;) + table=??(lr_in_chk_pkt_len ), priority=50 , match=(outport == "lr0-sw0"), action=(reg9[[1]] = check_pkt_larger(1414); next;) + table=??(lr_in_chk_pkt_len ), priority=55 , match=(outport == "lr0-sw0" && (tcp)), action=(next;) + table=??(lr_in_larger_pkts ), priority=0 , match=(1), action=(next;) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };) +- table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };) ++ table=??(lr_in_larger_pkts ), priority=150 , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };) + ]) + + check ovn-nbctl --wait=sb clear logical_router_port lr0-sw0 options +@@ -7361,9 +7397,9 @@ AT_CHECK([grep lr_in_unsnat lrflows | grep ct_snat | sed 's/table=../table=??/' + ]) + + AT_CHECK([grep lr_out_snat lrflows | grep ct_snat | sed 's/table=../table=??/' | sort], [0], [dnl +- table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat(172.16.1.10);) +- table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2")), action=(ct_snat(10.0.0.10);) +- table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3")), action=(ct_snat(192.168.0.10);) ++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.10);) ++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2") && (!ct.trk || !ct.rpl)), action=(ct_snat(10.0.0.10);) ++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3") && (!ct.trk || !ct.rpl)), action=(ct_snat(192.168.0.10);) + ]) + + check ovn-nbctl --wait=sb lr-nat-del DR snat 20.0.0.10 +@@ -7437,9 +7473,9 @@ AT_CHECK([grep lr_in_unsnat lrflows | grep ct_snat | sed 's/table=../table=??/' + ]) + + AT_CHECK([grep lr_out_snat lrflows | grep ct_snat | sed 's/table=../table=??/' | sort], [0], [dnl +- table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat(172.16.1.10);) +- table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2")), action=(ct_snat(10.0.0.10);) +- table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3")), action=(ct_snat(192.168.0.10);) ++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.10);) ++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2") && (!ct.trk || !ct.rpl)), action=(ct_snat(10.0.0.10);) ++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3") && (!ct.trk || !ct.rpl)), action=(ct_snat(192.168.0.10);) + ]) + + AT_CHECK([grep lr_in_dnat lrflows | grep ct_dnat | sed 's/table=../table=??/' | sort], [0], [dnl +@@ -8618,7 +8654,7 @@ AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb' | sort + AT_CHECK([ovn-nbctl --wait=sb lsp-set-options ln_port localnet_learn_fdb=true]) + AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb' | sort | sed 's/table=./table=?/'], [0], [dnl + table=? (ls_in_lookup_fdb ), priority=0 , match=(1), action=(next;) +- table=? (ls_in_lookup_fdb ), priority=100 , match=(inport == "ln_port"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;) ++ table=? (ls_in_lookup_fdb ), priority=100 , match=(inport == "ln_port"), action=(flags.localnet = 1; reg0[[11]] = lookup_fdb(inport, eth.src); next;) + table=? (ls_in_put_fdb ), priority=0 , match=(1), action=(next;) + table=? (ls_in_put_fdb ), priority=100 , match=(inport == "ln_port" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;) + ]) +@@ -8701,7 +8737,7 @@ AT_CHECK([grep "ls_in_lb " S1flows | sed 's/table=../table=??/' | sort], [0], [d + + ovn-sbctl get datapath S0 _uuid > dp_uuids + ovn-sbctl get datapath S1 _uuid >> dp_uuids +-lb_dp_group=$(ovn-sbctl --bare --columns datapath_group find Load_Balancer name=lb0) ++lb_dp_group=$(ovn-sbctl --bare --columns ls_datapath_group find Load_Balancer name=lb0) + AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl + | grep -A1 $lb_dp_group | tail -1 | tr ' ' '\n' | sort], [0], [dnl + $(cat dp_uuids | sort) +@@ -8985,7 +9021,6 @@ grep -c mutate], [0], [2 + # Pause ovn-northd and add/remove few addresses. when it is resumed + # it should use mutate for updating the address sets. + check as northd ovn-appctl -t NORTHD_TYPE pause +-check as northd-backup ovn-appctl -t NORTHD_TYPE pause + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats + check ovn-nbctl add address_set $foo_as_uuid addresses 1.1.1.5 +@@ -10008,14 +10043,21 @@ ovs-vsctl add-br br-phys + ovn_attach n1 br-phys 192.168.0.11 + + check_recompute_counter() { ++ northd_recomp_min=$1 ++ northd_recomp_max=$2 ++ lflow_recomp_min=$3 ++ lflow_recomp_max=$4 ++ sync_sb_pb_recomp_min=$5 ++ sync_sb_pb_recomp_max=$6 ++ + northd_recomp=$(as northd ovn-appctl -t NORTHD_TYPE inc-engine/show-stats northd recompute) +- AT_CHECK([test x$northd_recomp = x$1]) ++ AT_CHECK([test $northd_recomp -ge $northd_recomp_min && test $northd_recomp -le $northd_recomp_max]) + + lflow_recomp=$(as northd ovn-appctl -t NORTHD_TYPE inc-engine/show-stats lflow recompute) +- AT_CHECK([test x$lflow_recomp = x$2]) ++ AT_CHECK([test $lflow_recomp -ge $lflow_recomp_min && test $lflow_recomp -le $lflow_recomp_max]) + + sync_sb_pb_recomp=$(as northd ovn-appctl -t NORTHD_TYPE inc-engine/show-stats sync_to_sb_pb recompute) +- AT_CHECK([test x$sync_sb_pb_recomp = x$3]) ++ AT_CHECK([test $sync_sb_pb_recomp -ge $sync_sb_pb_recomp_min && test $sync_sb_pb_recomp -le $sync_sb_pb_recomp_max]) + } + + check ovn-nbctl --wait=hv ls-add ls0 +@@ -10032,29 +10074,29 @@ check ovn-nbctl --wait=hv lsp-add ls0 lsp0-0 -- lsp-set-addresses lsp0-0 "unknow + ovs-vsctl add-port br-int lsp0-0 -- set interface lsp0-0 external_ids:iface-id=lsp0-0 + wait_for_ports_up + check ovn-nbctl --wait=hv sync +-check_recompute_counter 5 5 5 ++check_recompute_counter 4 5 5 5 5 5 + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats + check ovn-nbctl --wait=hv lsp-add ls0 lsp0-1 -- lsp-set-addresses lsp0-1 "aa:aa:aa:00:00:01 192.168.0.11" + ovs-vsctl add-port br-int lsp0-1 -- set interface lsp0-1 external_ids:iface-id=lsp0-1 + wait_for_ports_up + check ovn-nbctl --wait=hv sync +-check_recompute_counter 0 0 0 ++check_recompute_counter 0 0 0 0 0 0 + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats + check ovn-nbctl --wait=hv lsp-add ls0 lsp0-2 -- lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:02 192.168.0.12" + ovs-vsctl add-port br-int lsp0-2 -- set interface lsp0-2 external_ids:iface-id=lsp0-2 + wait_for_ports_up + check ovn-nbctl --wait=hv sync +-check_recompute_counter 0 0 0 ++check_recompute_counter 0 0 0 0 0 0 + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats + check ovn-nbctl --wait=hv lsp-del lsp0-1 +-check_recompute_counter 0 0 0 ++check_recompute_counter 0 0 0 0 0 0 + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats + check ovn-nbctl --wait=hv lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:88 192.168.0.88" +-check_recompute_counter 0 0 0 ++check_recompute_counter 0 0 0 0 0 0 + + # Delete and re-add a LSP for several times continuously, to ensure + # frequent operations do not trigger recompute when there are in-flight +@@ -10068,14 +10110,14 @@ for i in $(seq 10); do + check ovn-nbctl lsp-del lsp0-2 + check ovn-nbctl lsp-add ls0 lsp0-2 -- lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:02 192.168.0.12" + done +-check_recompute_counter 0 0 0 ++check_recompute_counter 0 0 0 0 0 0 + + # No change, no recompute + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats + check ovn-nbctl --wait=sb sync +-check_recompute_counter 0 0 0 ++check_recompute_counter 0 0 0 0 0 0 + +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + + # Associate DHCP for lsp0-2 + ovn-nbctl dhcp-options-create 192.168.0.0/24 +@@ -10085,9 +10127,9 @@ ovn-nbctl dhcp-options-set-options $CIDR_UUID lease_time=3600 router=192.168. + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats + ovn-nbctl --wait=sb lsp-set-dhcpv4-options lsp0-2 $CIDR_UUID +-check_recompute_counter 0 0 0 ++check_recompute_counter 0 0 0 0 0 0 + +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + + # Add IPv6 address and associate DHCPv6 for lsp0-2 + check ovn-nbctl lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:01 192.168.0.11 aef0::4" +@@ -10096,9 +10138,9 @@ options="\"server_id\"=\"00:00:00:10:00:01\"")" + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats + ovn-nbctl --wait=sb lsp-set-dhcpv6-options lsp0-2 ${d1} +-check_recompute_counter 0 0 0 ++check_recompute_counter 0 0 0 0 0 0 + +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + + check ovn-nbctl --wait=hv ls-del ls0 + +@@ -10125,11 +10167,11 @@ ovn-nbctl lsp-add ls0 ls0-lr0 + ovn-nbctl lsp-set-type ls0-lr0 router + ovn-nbctl lsp-set-addresses ls0-lr0 router + check ovn-nbctl --wait=sb lsp-set-options ls0-lr0 router-port=lr0-ls0 +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + + ovn-nbctl lb-add lb0 192.168.0.10:80 10.0.0.10:8080 + check ovn-nbctl --wait=sb ls-lb-add ls0 lb0 +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats + # Add a lsp. northd and lflow engine shouldn't recompute even though this is +@@ -10216,14 +10258,14 @@ check ovn-nbctl --wait=sb meter-add m drop 1 pktps + check ovn-nbctl --wait=sb acl-add ls from-lport 1 1 allow + dnl Only triggers recompute of the sync_meters and lflow nodes. + check_recompute_counter 0 2 2 +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats + check ovn-nbctl --wait=sb meter-del m + check ovn-nbctl --wait=sb acl-del ls + dnl Only triggers recompute of the sync_meters and lflow nodes. + check_recompute_counter 0 2 2 +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + + AT_CLEANUP + ]) +@@ -10339,7 +10381,7 @@ wait_for_ports_up sw0-r1 + check_column $remote_chassis_uuid Port_Binding chassis logical_port=sw0-r1 + + # Set the type to router and ovn-northd should not claim it. +-check ovn-nbctl lsp-set-type sw0-r1 router ++check ovn-nbctl --wait=hv lsp-set-type sw0-r1 router + check_column '' Port_Binding chassis logical_port=sw0-r1 + + AT_CLEANUP +@@ -10391,34 +10433,39 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats + + check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + + check ovn-nbctl --wait=sb set load_balancer . options:foo=bar + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + + check ovn-nbctl --wait=sb -- lb-add lb2 20.0.0.10:80 20.0.0.20:80 -- lb-add lb3 30.0.0.10:80 30.0.0.20:80 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats + + check ovn-nbctl --wait=sb -- lb-del lb2 -- lb-del lb3 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats + + AT_CHECK([ovn-nbctl --wait=sb \ +@@ -10429,6 +10476,7 @@ AT_CHECK([ovn-nbctl --wait=sb \ + check_engine_stats lb_data norecompute compute + check_engine_stats northd recompute nocompute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + + # Any change to load balancer health check should also result in full recompute + # of northd node (but not northd_lb_data node) +@@ -10437,6 +10485,7 @@ check ovn-nbctl --wait=sb set load_balancer_health_check . options:foo=bar1 + check_engine_stats lb_data norecompute compute + check_engine_stats northd recompute nocompute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + + # Delete the health check from the load balancer. northd engine node should do a full recompute. + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10444,6 +10493,7 @@ check ovn-nbctl --wait=sb clear Load_Balancer . health_check + check_engine_stats lb_data norecompute compute + check_engine_stats northd recompute nocompute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats + check ovn-nbctl ls-add sw0 +@@ -10457,6 +10507,7 @@ ovn-nbctl --wait=sb lsp-set-options sw0-lr0 router-port=lr0-sw0 + check_engine_stats lb_data norecompute compute + check_engine_stats northd recompute nocompute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + + # Associate lb1 to sw0. There should be no recompute of northd engine node + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10464,7 +10515,11 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb1 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++# A LB applied to a switch/router triggers: ++# - a recompute in the first iteration (handling northd change) ++# - a compute in the second iteration (handling SB update) ++check_engine_stats sync_to_sb_lb recompute compute ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + + # Modify the backend of the lb1 vip + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10472,7 +10527,8 @@ check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.1 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++check_engine_stats sync_to_sb_lb recompute compute ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + + # Cleanup the vip of lb1. + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10480,7 +10536,8 @@ check ovn-nbctl --wait=sb clear load_Balancer lb1 vips + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++check_engine_stats sync_to_sb_lb recompute compute ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + + # Set the vips of lb1 back + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10488,7 +10545,8 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++check_engine_stats sync_to_sb_lb recompute compute ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + + # Add another vip to lb1 + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10496,7 +10554,8 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++check_engine_stats sync_to_sb_lb recompute compute ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + + # Disassociate lb1 from sw0. There should be a full recompute of northd engine node. + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10504,7 +10563,8 @@ check ovn-nbctl --wait=sb ls-lb-del sw0 lb1 + check_engine_stats lb_data norecompute compute + check_engine_stats northd recompute nocompute + check_engine_stats lflow recompute nocompute +-CHECK_NO_CHANGE_AFTER_RECOMPUTE ++check_engine_stats sync_to_sb_lb recompute compute ++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1) + + # Associate lb1 to sw0 and also create a port sw0p1. This should not result in + # full recompute of northd, but should rsult in full recompute of lflow node. +@@ -10513,6 +10573,7 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb1 -- lsp-add sw0 sw0p1 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + + CHECK_NO_CHANGE_AFTER_RECOMPUTE + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10523,6 +10584,7 @@ check ovn-nbctl --wait=sb ls-lb-del sw0 lb1 + check_engine_stats lb_data norecompute compute + check_engine_stats northd recompute nocompute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + # Add lb1 to lr0 and then disassociate +@@ -10531,6 +10593,7 @@ check ovn-nbctl --wait=sb lr-lb-add lr0 lb1 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + # Modify the backend of the lb1 vip +@@ -10539,6 +10602,7 @@ check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.1 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + # Cleanup the vip of lb1. +@@ -10547,6 +10611,7 @@ check ovn-nbctl --wait=sb clear load_Balancer lb1 vips + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + # Set the vips of lb1 back +@@ -10555,6 +10620,7 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + # Add another vip to lb1 +@@ -10563,6 +10629,7 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10570,14 +10637,16 @@ check ovn-nbctl --wait=sb lr-lb-del lr0 lb1 + check_engine_stats lb_data norecompute compute + check_engine_stats northd recompute nocompute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + # Test load balancer group now + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +-lbg1_uuid=$(ovn-nbctl create load_balancer_group name=lbg1) ++lbg1_uuid=$(ovn-nbctl --wait=sb create load_balancer_group name=lbg1) + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + + CHECK_NO_CHANGE_AFTER_RECOMPUTE + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10586,31 +10655,35 @@ lb1_uuid=$(fetch_column nb:Load_Balancer _uuid) + + # Add lb to the lbg1 group + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +-check ovn-nbctl add load_balancer_group . load_Balancer $lb1_uuid ++check ovn-nbctl --wait=sb add load_balancer_group . load_Balancer $lb1_uuid + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +-check ovn-nbctl clear load_balancer_group . load_Balancer ++check ovn-nbctl --wait=sb clear load_balancer_group . load_Balancer + check_engine_stats lb_data norecompute compute + check_engine_stats northd recompute nocompute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + + # Add back lb to the lbg1 group + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +-check ovn-nbctl add load_balancer_group . load_Balancer $lb1_uuid ++check ovn-nbctl --wait=sb add load_balancer_group . load_Balancer $lb1_uuid + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +-check ovn-nbctl add logical_switch sw0 load_balancer_group $lbg1_uuid ++check ovn-nbctl --wait=sb add logical_switch sw0 load_balancer_group $lbg1_uuid + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + + # Update lb and this should not result in northd recompute + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10618,6 +10691,7 @@ check ovn-nbctl --wait=sb set load_balancer . options:bar=foo + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + + # Modify the backend of the lb1 vip + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10625,6 +10699,7 @@ check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.1 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + # Cleanup the vip of lb1. +@@ -10633,6 +10708,7 @@ check ovn-nbctl --wait=sb clear load_Balancer lb1 vips + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + # Set the vips of lb1 back +@@ -10641,6 +10717,7 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + # Add another vip to lb1 +@@ -10649,19 +10726,22 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +-check ovn-nbctl clear logical_switch sw0 load_balancer_group ++check ovn-nbctl --wait=sb clear logical_switch sw0 load_balancer_group + check_engine_stats lb_data norecompute compute + check_engine_stats northd recompute nocompute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats + check ovn-nbctl add logical_router lr0 load_balancer_group $lbg1_uuid + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + # Modify the backend of the lb1 vip +@@ -10670,6 +10750,7 @@ check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.1 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + # Cleanup the vip of lb1. +@@ -10678,6 +10759,7 @@ check ovn-nbctl --wait=sb clear load_Balancer lb1 vips + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + # Set the vips of lb1 back +@@ -10686,6 +10768,7 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + # Add another vip to lb1 +@@ -10694,27 +10777,31 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +-check ovn-nbctl clear logical_router lr0 load_balancer_group ++check ovn-nbctl --wait=sb clear logical_router lr0 load_balancer_group + check_engine_stats lb_data norecompute compute + check_engine_stats northd recompute nocompute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + + # Add back lb group to logical switch and then delete it. + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +-check ovn-nbctl add logical_switch sw0 load_balancer_group $lbg1_uuid ++check ovn-nbctl --wait=sb add logical_switch sw0 load_balancer_group $lbg1_uuid + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +-check ovn-nbctl clear logical_switch sw0 load_balancer_group -- \ ++check ovn-nbctl --wait=sb clear logical_switch sw0 load_balancer_group -- \ + destroy load_balancer_group $lbg1_uuid + check_engine_stats lb_data norecompute compute + check_engine_stats northd recompute nocompute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb compute compute + + CHECK_NO_CHANGE_AFTER_RECOMPUTE + +@@ -10733,29 +10820,33 @@ lb3_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb3) + lb4_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb4) + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +-lbg1_uuid=$(ovn-nbctl create load_balancer_group name=lbg1) ++lbg1_uuid=$(ovn-nbctl --wait=sb create load_balancer_group name=lbg1) + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +-check ovn-nbctl set load_balancer_group . load_balancer="$lb2_uuid,$lb3_uuid,$lb4_uuid" ++check ovn-nbctl --wait=sb set load_balancer_group . load_balancer="$lb2_uuid,$lb3_uuid,$lb4_uuid" + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +-check ovn-nbctl set logical_switch sw0 load_balancer_group=$lbg1_uuid ++check ovn-nbctl --wait=sb set logical_switch sw0 load_balancer_group=$lbg1_uuid + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +-check ovn-nbctl set logical_router lr1 load_balancer_group=$lbg1_uuid ++check ovn-nbctl --wait=sb set logical_router lr1 load_balancer_group=$lbg1_uuid + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10763,6 +10854,7 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb2 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10770,6 +10862,7 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb3 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10778,6 +10871,7 @@ check ovn-nbctl --wait=sb lr-lb-add lr1 lb2 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10785,6 +10879,7 @@ check ovn-nbctl --wait=sb ls-lb-del sw0 lb2 + check_engine_stats lb_data norecompute compute + check_engine_stats northd recompute nocompute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10792,6 +10887,7 @@ check ovn-nbctl --wait=sb lr-lb-del lr1 lb2 + check_engine_stats lb_data norecompute compute + check_engine_stats northd recompute nocompute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute nocompute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + # Deleting lb4 should not result in lflow recompute as it is +@@ -10801,6 +10897,7 @@ check ovn-nbctl --wait=sb lb-del lb4 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + # Deleting lb2 should result in lflow recompute as it is +@@ -10810,6 +10907,7 @@ check ovn-nbctl --wait=sb lb-del lb2 + check_engine_stats lb_data norecompute compute + check_engine_stats northd norecompute compute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute + CHECK_NO_CHANGE_AFTER_RECOMPUTE + + check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats +@@ -10817,7 +10915,61 @@ check ovn-nbctl --wait=sb remove load_balancer_group . load_balancer $lb3_uuid + check_engine_stats lb_data norecompute compute + check_engine_stats northd recompute nocompute + check_engine_stats lflow recompute nocompute ++check_engine_stats sync_to_sb_lb recompute compute ++CHECK_NO_CHANGE_AFTER_RECOMPUTE ++ ++AT_CLEANUP ++]) ++ ++OVN_FOR_EACH_NORTHD_NO_HV([ ++AT_SETUP([Load balancer incremental processing - batched updates]) ++ovn_start ++ ++# Check the scenario when a LB is created and quickly deleted (northd ++# processes this in a single iteration). ++ ++check ovn-nbctl ls-add sw0 ++lbg_uuid=$(ovn-nbctl --wait=sb create load_balancer_group name=lbg) ++check ovn-nbctl --wait=sb add logical_switch sw0 load_balancer_group $lbg_uuid ++ ++# Pause ovn-northd. ++sleep_northd ++ ++check ovn-nbctl lb-add lb-temp 50.0.0.10:80 50.0.0.20:8080 ++lb_temp_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb-temp) ++check ovn-nbctl add load_balancer_group $lbg_uuid load_balancer $lb_temp_uuid ++check ovn-nbctl lb-del lb-temp ++ ++# Let ovn-northd process all the updates that happened since it went to sleep. ++wake_up_northd ++ ++# Add a new load balancer to make sure northd still functions properly. ++check ovn-nbctl lb-add lb1 50.0.0.10:80 50.0.0.30:8080 ++lb1_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb1) ++check ovn-nbctl add load_balancer_group $lbg_uuid load_balancer $lb1_uuid ++ ++check ovn-nbctl --wait=sb sync + CHECK_NO_CHANGE_AFTER_RECOMPUTE + ++# Re-check the same scenario but now also batch the additional LB creation. ++sleep_northd ++check ovn-nbctl lb-add lb-temp 50.0.0.10:80 50.0.0.20:8080 ++lb_temp_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb-temp) ++check ovn-nbctl add load_balancer_group $lbg_uuid load_balancer $lb_temp_uuid ++check ovn-nbctl lb-del lb-temp ++ ++# Add a new load balancer to make sure northd still functions properly. ++check ovn-nbctl lb-add lb2 50.0.0.10:80 50.0.0.30:8080 ++lb2_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb2) ++check ovn-nbctl add load_balancer_group $lbg_uuid load_balancer $lb2_uuid ++ ++wake_up_northd ++check ovn-nbctl --wait=sb sync ++CHECK_NO_CHANGE_AFTER_RECOMPUTE ++ ++AT_CHECK([as northd ovn-appctl -t NORTHD_TYPE status], [0], [dnl ++Status: active ++]) ++ + AT_CLEANUP + ]) +diff --git a/tests/ovn.at b/tests/ovn.at +index e127530f6..13f5575be 100644 +--- a/tests/ovn.at ++++ b/tests/ovn.at +@@ -23,8 +23,11 @@ m4_divert_text([PREPARE_TESTS], + diff -u $exp_text.sorted $rcv_text.sorted + } + ovn_check_packets__ () { +- echo +- echo "$3: checking packets in $1 against $2:" ++ if [[ -n "$4" ]]; then ++ echo "$3: checking packets in $1 against $2: using $4" ++ else ++ echo "$3: checking packets in $1 against $2:" ++ fi + rcv_pcap=$1 + rcv_text=`echo "$rcv_pcap.packets" | sed 's/\.pcap//'` + exp_text=$2 +@@ -35,7 +38,13 @@ m4_divert_text([PREPARE_TESTS], + echo "rcv_n=$rcv_n exp_n=$exp_n" + test $rcv_n -ge $exp_n], + [dump_diff__ "$rcv_pcap" "$exp_text"]) +- sort $exp_text > expout ++ if [[ -n "$4" ]]; then ++ sort $exp_text | $4 > expout ++ cat $rcv_text | $4 > rcv_tmp ++ mv rcv_tmp $rcv_text ++ else ++ sort $exp_text > expout ++ fi + } + ovn_check_packets_remove_broadcast__ () { + echo "$3: checking packets in $1 against $2:" +@@ -54,14 +63,26 @@ m4_divert_text([PREPARE_TESTS], + } + ovn_wait_packets__ () { + echo "$3: waiting for packets from $2 at $1:" ++ if [[ -n "$4" ]]; then ++ echo "$3: checking packets from $2 at $1: using $4" ++ else ++ echo "$3: checking packets from $2 at $1:" ++ fi + rcv_pcap=$1 + rcv_text=`echo "$rcv_pcap.packets" | sed 's/\.pcap//'` + exp_text=$2 ++ if [[ -n "$4" ]]; then ++ cmd=$4 ++ else ++ cmd=: ++ fi + OVS_WAIT_UNTIL( + [$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $rcv_pcap > $rcv_text +- sort $exp_text > expout +- test x"$(sort $rcv_text | comm -2 -3 expout -)" = "x"], ++ sort $exp_text | $cmd > expout ++ test x"$(sort $rcv_text | $cmd | comm -2 -3 expout -)" = "x"], + [dump_diff__ "$rcv_pcap" "$exp_text"]) ++ cat $rcv_text | $cmd > rcv_tmp ++ mv rcv_tmp $rcv_text + } + ovn_wait_packets_uniq__ () { + echo "$3: waiting for packets from $2 at $1:" +@@ -140,7 +161,7 @@ m4_divert_text([PREPARE_TESTS], + + m4_define([OVN_CHECK_PACKETS], + [AT_CHECK([$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $1 ], [0], [ignore]) +- ovn_check_packets__ "$1" "$2" "__file__:__line__" ++ ovn_check_packets__ "$1" "$2" "__file__:__line__" $3 + AT_CHECK([sort $rcv_text], [0], [expout], [ignore], [dump_diff__ "$1" "$2"])]) + + m4_define([OVN_CHECK_PACKETS_REMOVE_BROADCAST], +@@ -2198,13 +2219,13 @@ reg9[5] = chk_ecmp_nh(); + + # commit_lb_aff + commit_lb_aff(vip = "172.16.0.123:8080", backend = "10.0.0.3:8080", proto = tcp, timeout = 30); +- encodes as learn(table=78,idle_timeout=30,delete_learned,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.16.0.123,nw_proto=6,tcp_dst=8080,load:0x1->NXM_NX_REG10[14],load:0xa000003->NXM_NX_REG4[],load:0x1f90->NXM_NX_REG8[0..15]) ++ encodes as learn(table=78,idle_timeout=30,delete_learned,cookie=0xaaaaaaaa,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.16.0.123,nw_proto=6,tcp_dst=8080,load:0x1->NXM_NX_REG10[14],load:0xa000003->NXM_NX_REG4[],load:0x1f90->NXM_NX_REG8[0..15]) + + commit_lb_aff(vip = "172.16.0.123", backend = "10.0.0.3", timeout = 30); +- encodes as learn(table=78,idle_timeout=30,delete_learned,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.16.0.123,load:0x1->NXM_NX_REG10[14],load:0xa000003->NXM_NX_REG4[]) ++ encodes as learn(table=78,idle_timeout=30,delete_learned,cookie=0xaaaaaaaa,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.16.0.123,load:0x1->NXM_NX_REG10[14],load:0xa000003->NXM_NX_REG4[]) + + commit_lb_aff(vip = "[::1]:8080", backend = "[::2]:8080", proto = tcp, timeout = 30); +- encodes as learn(table=78,idle_timeout=30,delete_learned,OXM_OF_METADATA[],eth_type=0x86dd,NXM_NX_IPV6_SRC[],ipv6_dst=::1,nw_proto=6,tcp_dst=8080,load:0x1->NXM_NX_REG10[14],load:0x2->NXM_NX_XXREG0[],load:0x1f90->NXM_NX_REG8[0..15]) ++ encodes as learn(table=78,idle_timeout=30,delete_learned,cookie=0xaaaaaaaa,OXM_OF_METADATA[],eth_type=0x86dd,NXM_NX_IPV6_SRC[],ipv6_dst=::1,nw_proto=6,tcp_dst=8080,load:0x1->NXM_NX_REG10[14],load:0x2->NXM_NX_XXREG0[],load:0x1f90->NXM_NX_REG8[0..15]) + + # chk_lb_aff() + reg9[6] = chk_lb_aff(); +@@ -3849,7 +3870,7 @@ OVN_FOR_EACH_NORTHD([ + AT_SETUP([VLAN transparency, passthru=true, multiple hosts, custom ethtype]) + ovn_start + +-ethtype=802.11ad ++ethtype=802.1ad + + check ovn-nbctl ls-add ls + check ovn-nbctl --wait=sb add Logical-Switch ls other_config vlan-passthru=true +@@ -4650,6 +4671,12 @@ OVN_POPULATE_ARP + + wait_for_ports_up + check ovn-nbctl --wait=hv sync ++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"],["hv2"]) ++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv2"],["hv1"]) ++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"],["hv_gw"]) ++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv_gw"],["hv1"]) ++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv2"],["hv_gw"]) ++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv_gw"],["hv2"]) + + # test_packet INPORT DST SRC ETHTYPE OUTPORT... + # +@@ -4849,6 +4876,13 @@ check ovn-nbctl --wait=hv sync + # for ARP resolution). + OVN_POPULATE_ARP + ++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"],["hv2"]) ++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv2"],["hv1"]) ++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv2"],["hv3"]) ++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv3"],["hv2"]) ++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"],["hv3"]) ++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv3"],["hv1"]) ++ + # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... + # + # This shell function causes a packet to be received on INPORT. The packet's +@@ -5320,10 +5354,11 @@ test_arp() { + } + + test_na() { +- local inport=$1 sha=$2 spa=$3 ++ local inport=$1 sha=$2 spa=$3 src=${4-$3} + local request=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff', src='${sha}')/ \ +- IPv6(dst='ff01::1', src='${spa}')/ \ +- ICMPv6ND_NA(tgt='${spa}')") ++ IPv6(dst='ff01::1', src='${src}')/ \ ++ ICMPv6ND_NA(tgt='${spa}')/ \ ++ ICMPv6NDOptDstLLAddr(lladdr='${sha}')") + + hv=hv`vif_to_hv $inport` + as $hv ovs-appctl netdev-dummy/receive vif$inport $request +@@ -5399,6 +5434,24 @@ for i in 1 2; do + done + done + ++# Make sure that we can update existing entry with ++# "always_learn_from_arp_request=false" even when the source of NA src is LLA. ++check ovn-sbctl --all destroy mac_binding ++check ovn-nbctl --wait=hv set logical_router lr0 options:always_learn_from_arp_request=false ++ ++sha="f0:00:00:00:00:11" ++spa6="fd00::abcd:1" ++ ++test_na 11 $sha $spa6 ++wait_row_count MAC_Binding 1 ip=\"$spa6\" mac=\"$sha\" ++ ++sha="f0:00:00:00:00:12" ++lla6="fe80::abcd:1" ++ ++test_na 11 $sha $spa6 $lla6 ++wait_row_count MAC_Binding 1 ip=\"$spa6\" mac=\"$sha\" ++check_row_count MAC_Binding 0 ip=\"$lla6\" ++ + # Gracefully terminate daemons + OVN_CLEANUP([hv1], [hv2]) + +@@ -6758,13 +6811,7 @@ test_dhcp() { + } + + compare_dhcp_packets() { +- received=$($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif$1-tx.pcap) +- expected=$(cat $1.expected) +- +- if test "$received" != "$expected"; then +- AT_CHECK_UNQUOTED([echo "$received"; tcpdump_hex "$received"], [0], +- [$(echo "$expected"; tcpdump_hex "$expected")]) +- fi ++ OVN_CHECK_PACKETS([hv1/vif$1-tx.pcap], [$1.expected]) + } + + AT_CAPTURE_FILE([sbflows]) +@@ -6962,8 +7009,9 @@ expected_dhcp_opts=0 + test_dhcp 11 2 f00000000002 07 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 + + # There is no reply for this. Check for the INFO log in ovn-controller.log +-AT_CHECK([test 1 = $(cat hv1/ovn-controller.log | \ +-grep "DHCPRELEASE f0:00:00:00:00:02 10.0.0.6" -c)]) ++OVS_WAIT_UNTIL( ++ [test 1 = $(cat hv1/ovn-controller.log | grep "DHCPRELEASE f0:00:00:00:00:02 10.0.0.6" -c) ++]) + + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets + AT_CHECK([cat 2.packets], [0], []) +@@ -7120,7 +7168,9 @@ ciaddr=`ip_to_hex 0 0 0 0` + request_ip=0 + expected_dhcp_opts="" + test_dhcp 18 1 f00000000001 04 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts +-AT_CHECK([grep -F -iq 'DHCPDECLINE from f0:00:00:00:00:01, 10.0.0.4 duplicated' hv1/ovn-controller.log], [0], []) ++OVS_WAIT_UNTIL( ++ [test 1 -le $(grep -F -i -c 'DHCPDECLINE from f0:00:00:00:00:01, 10.0.0.4 duplicated' hv1/ovn-controller.log) ++]) + + # Send Etherboot. + +@@ -7138,7 +7188,7 @@ ovn-nbctl dhcp-options-set-options $d3 \ + lease_time=3600 router=10.0.0.1 bootfile_name_alt=\"bootfile_name_alt\" \ + bootfile_name=\"bootfile\" + +-ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 $d3 ++ovn-nbctl --wait=hv lsp-set-dhcpv4-options ls1-lp1 $d3 + + offer_ip=`ip_to_hex 10 0 0 4` + server_ip=`ip_to_hex 10 0 0 1` +@@ -7156,9 +7206,6 @@ compare_dhcp_packets 1 + as northd + OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) + +-as northd-backup +-OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) +- + northd_version=$(ovn-sbctl get SB_Global . options:northd_internal_version | sed s/\"//g) + echo "northd version = $northd_version" + +@@ -7295,10 +7342,6 @@ check ovn-nbctl --wait=hv sync + # Start with 0 because the first request will not have NXT_RESUME + n_resume=0 + +-trim_zeros() { +- sed 's/\(00\)\{1,\}$//' +-} +- + # This shell function sends a DHCPv6 request packet + # test_dhcpv6 INPORT SRC_MAC SRC_LLA DHCPv6_MSG_TYPE OFFER_IP OUTPORT... + # The OUTPORTs (zero or more) list the VIFs on which the original DHCPv6 +@@ -7400,12 +7443,8 @@ test_dhcpv6_release() { + check_packets() { + local port=$1 + +- $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif$port-tx.pcap | trim_zeros > $port.packets + # Skipping UDP checksum +- cat $port.expected | cut -c 1-120,125- > expout +- AT_CHECK([cat $port.packets | cut -c 1-120,125- ], [0], [expout]) +- +- rm $port.packets ++ OVN_CHECK_PACKETS([hv1/vif$port-tx.pcap], [$port.expected], ["trim_zeros | cut -c 1-120,125-"]) + rm $port.expected + } + +@@ -7508,7 +7547,7 @@ ovn-nbctl dhcp-options-set-options $d1 \ + server_id=00:00:00:10:00:01 \ + bootfile_name_alt=\"bootfile_name_alt\" \ + bootfile_name=\"bootfile_name\" +-ovn-nbctl lsp-set-dhcpv6-options ls1-lp2 ${d1} ++ovn-nbctl --wait=hv lsp-set-dhcpv6-options ls1-lp2 ${d1} + + reset_pcap_file hv1-vif2 hv1/vif2 + +@@ -7645,6 +7684,9 @@ ovn-nbctl lsp-add alice alice1 \ + wait_for_ports_up + check ovn-nbctl --wait=hv sync + ++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"],["hv2"]) ++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv2"],["hv1"]) ++ + # Send ip packets between foo1 and alice1 + src_mac="f00000010203" + dst_mac="000001010203" +@@ -8418,6 +8460,7 @@ check_dynamic_addresses() { + check_row_count nb:Logical_Switch_Port 1 name="$1" dynamic_addresses="$arg" + } + ++check ovn-nbctl --wait=sb sync + # Add a port to a switch that does not have a subnet set, then set the + # subnet which should result in an address being allocated for the port. + ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00" +@@ -8733,7 +8776,7 @@ OVN_FOR_EACH_NORTHD([ + AT_SETUP([ipam connectivity]) + ovn_start + +-ovn-nbctl lr-add R1 ++ovn-nbctl --wait=sb lr-add R1 + + # Test for a ping using dynamically allocated addresses. + ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00" +@@ -9019,10 +9062,6 @@ packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111 + # Send IP packet destined to 8.8.8.8 from lsp1lp2 + as hv1 ovs-appctl netdev-dummy/receive hv1-ls1lp2 $packet + +-trim_zeros() { +- sed 's/\(00\)\{1,\}$//' +-} +- + # ARP packet should be received with Target IP Address set to 192.168.1.254 and + # not 8.8.8.8 + +@@ -9078,9 +9117,6 @@ AT_CAPTURE_FILE([sbflows]) + + # Wait for packet to be received. + OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 140]) +-trim_zeros() { +- sed 's/\(00\)\{1,\}$//' +-} + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros |sort | uniq > packets + AT_CHECK([sort packets], [0], [dnl + fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001 +@@ -9277,9 +9313,6 @@ ovn-nbctl list logical_router_port lrp0 + ovn-nbctl show + # Wait for packet to be received. + OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 50]) +-trim_zeros() { +- sed 's/\(00\)\{1,\}$//' +-} + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | sort | uniq > packets + expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001" + echo $expected > expout +@@ -9300,9 +9333,6 @@ ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router" exclud + + # Wait for packets to be received. + OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 250]) +-trim_zeros() { +- sed 's/\(00\)\{1,\}$//' +-} + + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets + g0="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001" +@@ -10721,10 +10751,6 @@ OVN_POPULATE_ARP + wait_for_ports_up + check ovn-nbctl --wait=hv sync + +-trim_zeros() { +- sed 's/\(00\)\{1,\}$//' +-} +- + # Send ip packets between foo1 and bar1 + # (East-west traffic should flow normally) + src_mac="f00000010203" +@@ -11004,12 +11030,9 @@ test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d + # NXT_RESUMEs should be 1. + OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets +-cat 1.expected | cut -c -48 > expout +-AT_CHECK([cat 1.packets | cut -c -48], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c -48"]) + # Skipping the IPv4 checksum. +-cat 1.expected | cut -c 53- > expout +-AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 53-"]) + + reset_pcap_file hv1-vif1 hv1/vif1 + reset_pcap_file hv1-vif2 hv1/vif2 +@@ -11026,12 +11049,9 @@ test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d + # NXT_RESUMEs should be 2. + OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets +-cat 2.expected | cut -c -48 > expout +-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c -48"]) + # Skipping the IPv4 checksum. +-cat 2.expected | cut -c 53- > expout +-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c 53-"]) + + reset_pcap_file hv1-vif1 hv1/vif1 + reset_pcap_file hv1-vif2 hv1/vif2 +@@ -11049,12 +11069,9 @@ test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d + # NXT_RESUMEs should be 3. + OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets +-cat 2.expected | cut -c -48 > expout +-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c -48"]) + # Skipping the IPv4 checksum. +-cat 2.expected | cut -c 53- > expout +-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c 53-"]) + + reset_pcap_file hv1-vif1 hv1/vif1 + reset_pcap_file hv1-vif2 hv1/vif2 +@@ -11131,12 +11148,9 @@ test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d + # NXT_RESUMEs should be 5. + OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets +-cat 2.expected | cut -c -48 > expout +-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c -48"]) + # Skipping the IPv4 checksum. +-cat 2.expected | cut -c 53- > expout +-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c 53-"]) + + reset_pcap_file hv1-vif1 hv1/vif1 + reset_pcap_file hv1-vif2 hv1/vif2 +@@ -11154,12 +11168,9 @@ test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d + # NXT_RESUMEs should be 6. + OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets +-cat 2.expected | cut -c -48 > expout +-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c -48"]) + # Skipping the IPv4 checksum. +-cat 2.expected | cut -c 53- > expout +-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c 53-"]) + + reset_pcap_file hv1-vif1 hv1/vif1 + reset_pcap_file hv1-vif2 hv1/vif2 +@@ -11221,12 +11232,9 @@ test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d + # NXT_RESUMEs should be 9. + OVS_WAIT_UNTIL([test 9 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets +-cat 1.expected | cut -c -48 > expout +-AT_CHECK([cat 1.packets | cut -c -48], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c -48"]) + # Skipping the IPv4 checksum. +-cat 1.expected | cut -c 53- > expout +-AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 53-"]) + + reset_pcap_file hv1-vif1 hv1/vif1 + reset_pcap_file hv1-vif2 hv1/vif2 +@@ -11244,10 +11252,8 @@ test_dns6 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $ + # NXT_RESUMEs should be 10 + OVS_WAIT_UNTIL([test 10 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets + # Skipping the UDP checksum. +-cat 1.expected | cut -c 1-120,125- > expout +-AT_CHECK([cat 1.packets | cut -c 1-120,125-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 1-120,125-"]) + + reset_pcap_file hv1-vif1 hv1/vif1 + reset_pcap_file hv1-vif2 hv1/vif2 +@@ -11273,12 +11279,9 @@ test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d + # NXT_RESUMEs should be 11. + OVS_WAIT_UNTIL([test 11 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets +-cat 1.expected | cut -c -48 > expout +-AT_CHECK([cat 1.packets | cut -c -48], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c -48"]) + # Skipping the IPv4 checksum. +-cat 1.expected | cut -c 53- > expout +-AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 53-"]) + + reset_pcap_file hv1-vif1 hv1/vif1 + reset_pcap_file hv1-vif2 hv1/vif2 +@@ -11296,12 +11299,9 @@ test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d + # NXT_RESUMEs should be 12. + OVS_WAIT_UNTIL([test 12 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets +-cat 1.expected | cut -c -48 > expout +-AT_CHECK([cat 1.packets | cut -c -48], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c -48"]) + # Skipping the IPv4 checksum. +-cat 1.expected | cut -c 53- > expout +-AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 53-"]) + + reset_pcap_file hv1-vif1 hv1/vif1 + reset_pcap_file hv1-vif2 hv1/vif2 +@@ -11481,30 +11481,7 @@ test_ip_packet() + # Resend packet from foo1 to outside1 + check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet + +- AT_CAPTURE_FILE([exp]) +- AT_CAPTURE_FILE([rcv]) +- check_packets() { +- > exp +- > rcv +- +- pcap=ext1/vif1-tx.pcap +- type=ext1-vif1.expected +- echo "--- $pcap" | tee -a exp >> rcv +- sort -u "$type" >> exp +- $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | sort -u >> rcv +- echo | tee -a exp >> rcv +- +- pcap=$active_gw/br-phys_n1-tx.pcap +- echo "--- $pcap" | tee -a exp >> rcv +- sort -u "$type" >> exp +- $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap > packets +- (grep "$expected" packets; grep "$exp_gw_ip_garp" packets) | sort -u >> rcv +- echo | tee -a exp >> rcv +- +- $at_diff exp rcv >/dev/null +- } +- +- OVS_WAIT_UNTIL([check_packets], [$at_diff -F'^---' exp rcv]) ++ OVN_CHECK_PACKETS_CONTAIN([ext1/vif1-tx.pcap], [ext1-vif1.expected]) + + if test $backup_vswitchd_dead != 1; then + # Check for backup gw only if vswitchd is alive +@@ -12206,9 +12183,6 @@ OVN_WAIT_PATCH_PORT_FLOWS(["ln_port"], ["hv2"]) + + # Wait for packets to be received. + OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100]) +-trim_zeros() { +- sed 's/\(00\)\{1,\}$//' +-} + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets + expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001" + echo $expected > expout +@@ -12240,21 +12214,12 @@ OVN_WAIT_PATCH_PORT_FLOWS(["ln_port"], ["hv3"]) + # Re-add nat-addresses option + ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router" + +-# Wait for packets to be received. +-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 250]) +-trim_zeros() { +- sed 's/\(00\)\{1,\}$//' +-} +- +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | sort | uniq > packets + garp_1="fffffffffffff0000000000308060001080006040001f00000000003c0a80003000000000000c0a80003" +-echo $garp_1 > expout ++echo $garp_1 > expected_out + garp_2="fffffffffffff0000000000408060001080006040001f00000000004c0a80004000000000000c0a80004" +-echo $garp_2 >> expout ++echo $garp_2 >> expected_out + +-cat packets | grep $garp_1 | head -1 > exp +-cat packets | grep $garp_2 | head -1 >> exp +-AT_CHECK([cat exp], [0], [expout]) ++OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [expected_out], "trim_zeros") + + OVN_CLEANUP([hv1],[hv2],[hv3]) + +@@ -12577,6 +12542,7 @@ AT_CLEANUP + + OVN_FOR_EACH_NORTHD([ + AT_SETUP([IPv6 ND Router Solicitation responder]) ++AT_SKIP_IF([test $HAVE_SCAPY = no]) + AT_KEYWORDS([ovn-nd_ra]) + ovn_start + +@@ -12643,76 +12609,120 @@ ovs-vsctl -- add-port br-int hv1-vif3 -- \ + wait_for_ports_up + check ovn-nbctl --wait=hv sync + ++n_resume=1 ++ + # Make sure that ovn-controller has installed the corresponding OF Flow. + OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`]) + + # This shell function sends a Router Solicitation packet. +-# test_ipv6_ra INPORT SRC_MAC SRC_LLA ADDR_MODE MTU RA_PREFIX_OPT RDNSS DNSSL ROUTE_INFO ++# test_ipv6_ra INPORT SRC_MAC SRC_LLA RA_OPT MTU PREFIX RDNSS DNSSL ROUTES + test_ipv6_ra() { +- local inport=$1 src_mac=$2 src_lla=$3 addr_mode=$4 mtu=$5 prefix_opt=$6 +- local rdnss=$7 dnssl=$8 route_info=$9 +- local request=333300000002${src_mac}86dd6000000000103aff${src_lla}ff02000000000000000000000000000285000efc000000000101${src_mac} ++ local inport=$1 src_mac=$2 src_ip=$3 ra=$4 mtu=$5 prefix=$6 ++ local rdnss=$7 dnssl=$8 routes=$9 + +- local len=24 +- local mtu_opt="" +- if test $mtu != 0; then +- len=`expr $len + 8` +- mtu_opt=05010000${mtu} ++ local request=$(fmt_pkt "Ether(dst='33:33:00:00:00:02', src='${src_mac}')/ \ ++ IPv6(dst='ff02::2', src='${src_ip}')/ \ ++ ICMPv6ND_RS()/ \ ++ ICMPv6NDOptSrcLLAddr(lladdr='${src_mac}')") ++ ++ local reply_dst=$src_ip ++ if test "${reply_dst}" = "::"; then ++ reply_dst="ff02::1" + fi + +- if test ${#rdnss} != 0; then +- len=`expr $len + ${#rdnss} / 2` ++ local rep_scapy="Ether(dst='${src_mac}', src='fa:16:3e:00:00:01')/ \ ++ IPv6(dst='${reply_dst}', src='fe80::f816:3eff:fe00:1')/ \ ++ ${ra}/ \ ++ ICMPv6NDOptSrcLLAddr(lladdr='fa:16:3e:00:00:01')" ++ ++ if test "${mtu}" != "0"; then ++ rep_scapy="${rep_scapy}/ICMPv6NDOptMTU(mtu=${mtu})" + fi + +- if test ${#dnssl} != 0; then +- len=`expr $len + ${#dnssl} / 2` ++ if test -n "${rdnss}"; then ++ rep_scapy="${rep_scapy}/ICMPv6NDOptRDNSS(dns=[['${rdnss}']])" + fi + +- if test ${#route_info} != 0; then +- len=`expr $len + ${#route_info} / 2` ++ if test -n "${dnssl}"; then ++ rep_scapy="${rep_scapy}/ICMPv6NDOptDNSSL(searchlist=[['${dnssl}']])" + fi + +- if test ${#prefix_opt} != 0; then +- prefix_opt=${prefix_opt}fdad1234567800000000000000000000 +- len=`expr $len + ${#prefix_opt} / 2` ++ if test -n "${routes}"; then ++ rep_scapy="${rep_scapy}/${routes}" + fi + +- len=$(printf "%x" $len) +- local lrp_mac=fa163e000001 +- local lrp_lla=fe80000000000000f8163efffe000001 +- local reply=${src_mac}${lrp_mac}86dd6000000000${len}3aff${lrp_lla}${src_lla}8600XXXXff${addr_mode}ffff00000000000000000101${lrp_mac}${mtu_opt}${rdnss}${dnssl}${route_info}${prefix_opt} ++ if test -n "${prefix}"; then ++ local a_flag=$(echo $ra | grep -vc "M=1") ++ rep_scapy="${rep_scapy}/ICMPv6NDOptPrefixInfo(prefix='${prefix}', A=${a_flag})" ++ fi ++ ++ local reply=$(fmt_pkt "${rep_scapy}") + echo $reply >> $inport.expected + + as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $request ++ ++ OVS_WAIT_UNTIL([test $n_resume = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) ++ n_resume=$((n_resume + 1)) ++} ++ ++check_packets() { ++ local port=$1 ++ ++ # Skipping UDP checksum ++ OVN_CHECK_PACKETS([hv1/vif$port-tx.pcap], [$port.expected], ["cut -c 1-112,117-"]) ++ ++ rm $port.packets ++ rm $port.expected ++ ++ reset_pcap_file hv1-vif1 hv1/vif1 ++ reset_pcap_file hv1-vif2 hv1/vif2 ++ reset_pcap_file hv1-vif3 hv1/vif3 ++} ++ ++prepare_ra_opt() { ++ local mode=$1 priority=$2 ++ ++ if test "${mode}" = "stateful"; then ++ echo "ICMPv6ND_RA(chlim=255, routerlifetime=65535, M=1, prf=${priority})" ++ elif test "${mode}" = "stateless"; then ++ echo "ICMPv6ND_RA(chlim=255, routerlifetime=65535, O=1, prf=${priority})" ++ else ++ echo "ICMPv6ND_RA(chlim=255, routerlifetime=65535, prf=${priority})" ++ fi ++} ++ ++prepare_route_opt() { ++ local prefix=$1 priority=$2 plen=$3 ++ ++ local len=2 ++ if test $plen -gt 64; then ++ len=3 ++ fi ++ ++ echo "ICMPv6NDOptRouteInfo(len=$len, prf=${priority}, prefix='${prefix}', plen=$plen)" + } + + AT_CAPTURE_FILE([ofctl_monitor0.log]) + as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \ + --pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log + ++prefix="fdad:1234:5678::" ++ + # MTU is not set and the address mode is set to slaac +-addr_mode=00 +-default_prefix_option_config=030440c0ffffffffffffffff00000000 +-src_mac=fa163e000002 +-src_lla=fe80000000000000f8163efffe000002 +-test_ipv6_ra 1 $src_mac $src_lla $addr_mode 0 $default_prefix_option_config ++ra=$(prepare_ra_opt "" 0) ++src_mac="fa:16:3e:00:00:02" ++src_lla="fe80::f816:3eff:fe00:2" + +-# NXT_RESUME should be 1. +-OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) ++test_ipv6_ra 1 $src_mac $src_lla "$ra" 0 $prefix "" "" "" ++check_packets 1 + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets ++# Check with RA with src being "::". ++ovn-nbctl --wait=hv lsp-set-port-security lp1 "" + +-cat 1.expected | cut -c -112 > expout +-AT_CHECK([cat 1.packets | cut -c -112], [0], [expout]) ++test_ipv6_ra 1 $src_mac "::" "$ra" 0 $prefix "" "" "" ++check_packets 1 + +-# Skipping the ICMPv6 checksum. +-cat 1.expected | cut -c 117- > expout +-AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout]) +- +-rm -f *.expected +-reset_pcap_file hv1-vif1 hv1/vif1 +-reset_pcap_file hv1-vif2 hv1/vif2 +-reset_pcap_file hv1-vif3 hv1/vif3 ++ovn-nbctl --wait=hv lsp-set-port-security lp1 "fa:16:3e:00:00:02 10.0.0.12 fdad:1234:5678:0:f816:3eff:fe:2" + + # Set the MTU to 1500, send_periodic to false, preference to LOW + ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:mtu=1500 +@@ -12726,33 +12736,14 @@ ovn-nbctl --wait=hv set Logical_Router_port lrp0 ipv6_ra_configs:route_info=HIGH + OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`]) + + # addr_mode byte also includes router preference information +-addr_mode=18 +-default_prefix_option_config=030440c0ffffffffffffffff00000000 +-src_mac=fa163e000003 +-src_lla=fe80000000000000f8163efffe000003 +-mtu=000005dc +-rdnss=19030000ffffffff10000000000000000000000000000011 +-dnssl=1f030000ffffffff02616102626202636300000000000000 +-route_info=18023008ffffffff100100000000000018036018ffffffff10020000000000000000000000000000 +- +-test_ipv6_ra 2 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config $rdnss $dnssl $route_info +- +-# NXT_RESUME should be 2. +-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) +- +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets +- +-cat 2.expected | cut -c -112 > expout +-AT_CHECK([cat 2.packets | cut -c -112], [0], [expout]) +- +-# Skipping the ICMPv6 checksum. +-cat 2.expected | cut -c 117- > expout +-AT_CHECK([cat 2.packets | cut -c 117-], [0], [expout]) ++ra=$(prepare_ra_opt "" 3) ++routes=$(prepare_route_opt '1001::' 1 48) ++routes="${routes}/$(prepare_route_opt '1002::' 3 96)" ++src_mac="fa:16:3e:00:00:03" ++src_lla="fe80::f816:3eff:fe00:3" + +-rm -f *.expected +-reset_pcap_file hv1-vif1 hv1/vif1 +-reset_pcap_file hv1-vif2 hv1/vif2 +-reset_pcap_file hv1-vif3 hv1/vif3 ++test_ipv6_ra 2 $src_mac $src_lla "$ra" 1500 $prefix "1000::11" "aa.bb.cc" "$routes" ++check_packets 2 + + # Set the address mode to dhcpv6_stateful, router_preference to HIGH + ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateful +@@ -12763,30 +12754,12 @@ ovn-nbctl --wait=hv remove Logical_Router_Port lrp0 ipv6_ra_configs route_info + OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`]) + + # addr_mode byte also includes router preference information +-addr_mode=88 +-default_prefix_option_config=03044080ffffffffffffffff00000000 +-src_mac=fa163e000004 +-src_lla=fe80000000000000f8163efffe000004 +-mtu=000005dc +- +-test_ipv6_ra 3 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config "" $dnssl +- +-# NXT_RESUME should be 3. +-OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) +- +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap > 3.packets +- +-cat 3.expected | cut -c -112 > expout +-AT_CHECK([cat 3.packets | cut -c -112], [0], [expout]) +- +-# Skipping the ICMPv6 checksum. +-cat 3.expected | cut -c 117- > expout +-AT_CHECK([cat 3.packets | cut -c 117-], [0], [expout]) ++ra=$(prepare_ra_opt "stateful" 1) ++src_mac="fa:16:3e:00:00:04" ++src_lla="fe80::f816:3eff:fe00:4" + +-rm -f *.expected +-reset_pcap_file hv1-vif1 hv1/vif1 +-reset_pcap_file hv1-vif2 hv1/vif2 +-reset_pcap_file hv1-vif3 hv1/vif3 ++test_ipv6_ra 3 $src_mac $src_lla "$ra" 1500 $prefix "" "aa.bb.cc" "" ++check_packets 3 + + # Set the address mode to dhcpv6_stateless, reset router preference to default + ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateless +@@ -12795,46 +12768,27 @@ ovn-nbctl --wait=hv remove Logical_Router_Port lrp0 ipv6_ra_configs dnssl + # Make sure that ovn-controller has installed the corresponding OF Flow. + OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`]) + +-addr_mode=40 +-default_prefix_option_config=030440c0ffffffffffffffff00000000 +-src_mac=fa163e000002 +-src_lla=fe80000000000000f8163efffe000002 +-mtu=000005dc +- +-test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config +- +-# NXT_RESUME should be 4. +-OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) +- +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets ++ra=$(prepare_ra_opt "stateless" 0) ++src_mac="fa:16:3e:00:00:02" ++src_lla="fe80::f816:3eff:fe00:2" + +-cat 1.expected | cut -c -112 > expout +-AT_CHECK([cat 1.packets | cut -c -112], [0], [expout]) +- +-# Skipping the ICMPv6 checksum. +-cat 1.expected | cut -c 117- > expout +-AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout]) +- +-rm -f *.expected +-reset_pcap_file hv1-vif1 hv1/vif1 +-reset_pcap_file hv1-vif2 hv1/vif2 +-reset_pcap_file hv1-vif3 hv1/vif3 ++test_ipv6_ra 1 $src_mac $src_lla "$ra" 1500 $prefix "" "" "" ++check_packets 1 + + # Set the address mode to invalid. + ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=invalid + # Make sure that ovn-controller has not installed any OF Flow for IPv6 ND RA. + OVS_WAIT_UNTIL([test 0 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`]) + +-addr_mode=40 +-default_prefix_option_config="" +-src_mac=fa163e000002 +-src_lla=fe80000000000000f8163efffe000002 +-mtu=000005dc ++ra=$(prepare_ra_opt "stateless" 0) + +-test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config ++src_mac="fa:16:3e:00:00:02" ++src_lla="fe80::f816:3eff:fe00:2" + +-# NXT_RESUME should be 4 only. +-OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) ++# This shouldn't produce any NXT_RESUME. ++n_resume=$((n_resume - 1)) ++ ++test_ipv6_ra 1 $src_mac $src_lla "$ra" 1500 "" "" "" "" + + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets + AT_CHECK([cat 1.packets], [0], []) +@@ -13879,33 +13833,15 @@ as hv1 reset_pcap_file snoopvif hv1/snoopvif + # add nat-addresses option + ovn-nbctl --wait=hv lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router" + +-# Wait for packets to be received through hv2. +-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100]) +-trim_zeros() { +- sed 's/\(00\)\{1,\}$//' +-} +- + only_broadcast_from_lrp1() { + grep "fffffffffffff00000000001" + } + + garp="fffffffffffff0000000000108060001080006040001f00000000001c0a80064000000000000c0a80064" +-echo $garp > expout ++echo $garp > expected_out + +-OVS_WAIT_UNTIL( +- [$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap > rcv_text +- exp_rcvd=$(cat rcv_text | grep $garp | wc -l) +- echo "expected received = $exp_rcvd" +- test $exp_rcvd -ge 1]) +- +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv1_snoop_tx +-echo "packets on hv1-snoopvif:" +-cat hv1_snoop_tx +-AT_CHECK([sort hv1_snoop_tx], [0], [expout]) +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv2_br_phys_tx +-echo "packets on hv2 br-phys tx" +-cat hv2_br_phys_tx +-AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], [expout]) ++OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [expected_out], "trim_zeros") ++OVN_CHECK_PACKETS_CONTAIN([hv2/br-phys_n1-tx.pcap], [expected_out], "trim_zeros") + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv3/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv3_br_phys_tx + echo "packets on hv3 br-phys tx" + cat hv3_br_phys_tx +@@ -13914,9 +13850,6 @@ AT_CHECK([grep $garp hv3_br_phys_tx | sort], [0], []) + + # at this point, we invert the priority of the gw chassis between hv2 and hv3 + +-# Reset hv2/br-phys_n1 before inverting gw chassis, as otherwise packets might +-# be reset (while not resetting hv1/snoopvif because not yet sent). +-as hv2 reset_pcap_file br-phys_n1 hv2/br-phys_n1 + ovn-nbctl --wait=hv \ + --id=@gc0 create Gateway_Chassis \ + name=outside_gw1 chassis_name=hv2 priority=1 -- \ +@@ -13924,24 +13857,17 @@ ovn-nbctl --wait=hv \ + name=outside_gw2 chassis_name=hv3 priority=10 -- \ + set Logical_Router_Port lrp0 'gateway_chassis=[@gc0,@gc1]' + ++# We expect not to receive garp on hv2 after inverting the priority. ++# Hence reset hv2 after inverting priority as otherwise a garp might ++# be received on hv2 between the reset and the priority change. ++ ++as hv2 reset_pcap_file br-phys_n1 hv2/br-phys_n1 + as hv3 reset_pcap_file br-phys_n1 hv3/br-phys_n1 + as hv1 reset_pcap_file snoopvif hv1/snoopvif + +-trim_zeros() { +- sed 's/\(00\)\{1,\}$//' +-} +- +-# Wait for packets to be received. +-OVS_WAIT_UNTIL( +- [$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap > rcv_text +- exp_rcvd=$(cat rcv_text | grep $garp | wc -l) +- echo "expected received = $exp_rcvd" +- test $exp_rcvd -ge 1]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv1_snoopvif_tx +-AT_CHECK([sort hv1_snoopvif_tx], [0], [expout]) +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv3/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv3_br_phys_tx +-AT_CHECK([grep $garp hv3_br_phys_tx | sort], [0], [expout]) ++OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [expected_out], "trim_zeros") ++OVN_CHECK_PACKETS_CONTAIN([hv3/br-phys_n1-tx.pcap], [expected_out], "trim_zeros") + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv2_br_phys_tx + AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], []) + +@@ -13974,25 +13900,11 @@ ovn-nbctl --wait=hv lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="rout + OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns nat_addresses find port_binding \ + logical_port=lrp0-rp | grep is_chassis | wc -l`]) + +-# Wait for packets to be received. +-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100]) +-trim_zeros() { +- sed 's/\(00\)\{1,\}$//' +-} +- + garp="fffffffffffff00000000001810007de08060001080006040001f00000000001c0a80064000000000000c0a80064" +-echo $garp > expout ++echo $garp > expected_out + +-OVS_WAIT_UNTIL( +- [$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap > rcv_text +- exp_rcvd=$(cat rcv_text | grep $garp | wc -l) +- echo "expected received = $exp_rcvd" +- test $exp_rcvd -ge 1]) +- +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv1_snoopvif_tx +-AT_CHECK([sort hv1_snoopvif_tx], [0], [expout]) +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv3/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv3_br_phys_tx +-AT_CHECK([grep $garp hv3_br_phys_tx | sort], [0], [expout]) ++OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [expected_out], "trim_zeros") ++OVN_CHECK_PACKETS_CONTAIN([hv3/br-phys_n1-tx.pcap], [expected_out], "trim_zeros") + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv2_br_phys_tx + AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], []) + +@@ -14333,10 +14245,6 @@ wc -l], [0], [4 + chassis_uuid=$(fetch_column Chassis _uuid name=hv1) + wait_row_count Port_Binding 1 logical_port=cr-ip6_public chassis=$chassis_uuid + +-trim_zeros() { +- sed 's/\(00\)\{1,\}$//' +-} +- + # Test the IPv6 Neighbor Solicitation (NS) - nd_ns action for unknown MAC + # addresses. ovn-controller should generate an IPv6 NS request for IPv6 + # packets whose MAC is unknown (in the ARP_REQUEST router pipeline stage. +@@ -14888,6 +14796,10 @@ wait_column "$hv2_uuid" Port_Binding requested_additional_chassis logical_port=m + # ovn-installed on hv2 should guarantee that. + OVS_WAIT_UNTIL([test `as hv2 ovs-vsctl get Interface migrator external_ids:ovn-installed` = '"true"']) + ++# Still, this does not guarantee that all flows are installed on hv3: hv3 (might) still need to receive and handle ++# additional_chassis for migrator port ++ovn-nbctl --wait=hv sync ++ + # check that... + # unicast from First arrives to hv1:Migrator + # unicast from First arrives to hv2:Migrator +@@ -14974,6 +14886,9 @@ wait_column "" Port_Binding requested_additional_chassis logical_port=migrator + # For instance, migrator port might still be up from prior to complete migration to hv2 + OVS_WAIT_UNTIL([test `as hv2 ovs-vsctl get Interface migrator external_ids:ovn-installed` = '"true"']) + ++# Give time for hv3 to handle the change of Port_Binding for migrator port ++ovn-nbctl --wait=hv sync ++ + # check that... + # unicast from Third doesn't arrive to hv1:Migrator + # unicast from Third arrives to hv2:Migrator +@@ -16188,7 +16103,7 @@ echo "verifying that lsp0 binding moves when requested-chassis is changed" + + ovn-nbctl lsp-set-options lsp0 requested-chassis=hv2 + # We might see multiple "Releasing lport ...", when sb is read only +-OVS_WAIT_UNTIL([test 1 -le $(grep -c "Releasing lport lsp0 from this chassis" hv1/ovn-controller.log)]) ++OVS_WAIT_UNTIL([test 1 -le $(grep -c "Releasing lport lsp0" hv1/ovn-controller.log)]) + + wait_column "$hv2_uuid" Port_Binding chassis logical_port=lsp0 + +@@ -16276,7 +16191,7 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [0], [ig + AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep actions=output:1], [0], [ignore]) + + check ovn-nbctl --wait=hv lsp-set-options lsp0 requested-chassis=non-existant-chassis +-OVS_WAIT_UNTIL([test 1 -le $(grep -c "Releasing lport lsp0 from this chassis" hv1/ovn-controller.log)]) ++OVS_WAIT_UNTIL([test 1 -le $(grep -c "Releasing lport lsp0" hv1/ovn-controller.log)]) + check ovn-nbctl --wait=hv sync + wait_column '' Port_Binding chasssi logical_port=lsp0 + AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [1], []) +@@ -18989,6 +18904,46 @@ for sf in 0 1; do + done + done + ++check_packets() { ++ n_allowed=$1 ++ > expected ++ > received ++ for i in 1 2 3; do ++ echo "--- hv$i vif${i}1" | tee -a expected >> received ++ sort ${i}1.expected >> expected ++ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv$i/vif${i}1-tx.pcap | sort >> received ++ echo | tee -a expected >> received ++ done ++ ++ # need to verify the log for ACL hit as well, since in the allow case ++ # (unlike the drop case) it is tricky to pass just with the expected; ++ # since with the stateful rule the packet will still get by (default ++ # rule) even if it doesn't hit the allow rule. ++ # The hit count for the ACL is 6 (1 unicast + 2 non-unicast) * 2 ++ # (with/without stateful rule) for hv1 and hv2, each. ++ cat >>expected <>received </dev/null ++} ++ ++# We need to wait and check here that packets are received as they should as otherwise packets ++# which were just sent might by handled after setting next ACL (allow) rules. ++OVS_WAIT_UNTIL([check_packets 0], [$at_diff -F'^---' expected received]) ++ + # Test allow rule + #---------------- + ovn-nbctl acl-del lsw0 +@@ -19037,41 +18992,7 @@ as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int > offlows3 + # Now check the packets actually received against the ones expected. + AT_CAPTURE_FILE([expected]) + AT_CAPTURE_FILE([received]) +-check_packets() { +- > expected +- > received +- for i in 1 2 3; do +- echo "--- hv$i vif${i}1" | tee -a expected >> received +- sort ${i}1.expected >> expected +- $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv$i/vif${i}1-tx.pcap | sort >> received +- echo | tee -a expected >> received +- done +- +- # need to verify the log for ACL hit as well, since in the allow case +- # (unlike the drop case) it is tricky to pass just with the expected; +- # since with the stateful rule the packet will still get by (default +- # rule) even if it doesn't hit the allow rule. +- # The hit count for the ACL is 6 (1 unicast + 2 non-unicast) * 2 +- # (with/without stateful rule) for hv1 and hv2, each. +- cat >>expected <>received </dev/null +-} +-OVS_WAIT_UNTIL([check_packets], [$at_diff -F'^---' expected received]) ++OVS_WAIT_UNTIL([check_packets 6], [$at_diff -F'^---' expected received]) + + OVN_CLEANUP([hv1],[hv2],[hv3]) + +@@ -19790,11 +19711,6 @@ test_dhcp() { + as hv1 ovs-appctl netdev-dummy/receive hv${inport}-ext${inport} $request + } + +- +-trim_zeros() { +- sed 's/\(00\)\{1,\}$//' +-} +- + # This shell function sends a DHCPv6 request packet + # test_dhcpv6 INPORT SRC_MAC SRC_LLA DHCPv6_MSG_TYPE OFFER_IP OUTPORT... + # The OUTPORTs (zero or more) list the VIFs on which the original DHCPv6 +@@ -19876,12 +19792,9 @@ OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`]) + # NXT_RESUMEs should be 0 in hv2. + OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets +-cat ext1_v4.expected | cut -c -48 > expout +-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c -48"]) + # Skipping the IPv4 checksum. +-cat ext1_v4.expected | cut -c 53- > expout +-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c 53-"]) + + # ovs-ofctl also resumes the packets and this causes other ports to receive + # the DHCP request packet. So reset the pcap files so that its easier to test. +@@ -19903,13 +19816,9 @@ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`]) + # NXT_RESUMEs should be 0 in hv2. + OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \ +-sort > ext1_v6.packets +-cat ext1_v6.expected | cut -c -120 > expout +-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c -120"]) + # Skipping the UDP checksum +-cat ext1_v6.expected | cut -c 125- > expout +-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c 125-"]) + + rm -f ext1_v6.expected + rm -f ext1_v6.packets +@@ -19969,12 +19878,9 @@ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`]) + # NXT_RESUMEs should be 1 in hv2. + OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets +-cat ext1_v4.expected | cut -c -48 > expout +-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c -48"]) + # Skipping the IPv4 checksum. +-cat ext1_v4.expected | cut -c 53- > expout +-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c 53-"]) + + # ovs-ofctl also resumes the packets and this causes other ports to receive + # the DHCP request packet. So reset the pcap files so that its easier to test. +@@ -19995,13 +19901,9 @@ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`]) + # NXT_RESUMEs should be 2 in hv2. + OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \ +-sort > ext1_v6.packets +-cat ext1_v6.expected | cut -c -120 > expout +-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c -120"]) + # Skipping the UDP checksum +-cat ext1_v6.expected | cut -c 125- > expout +-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c 125-"]) + + rm -f ext1_v6.expected + rm -f ext1_v6.packets +@@ -20077,12 +19979,9 @@ OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`]) + # NXT_RESUMEs should be 2 in hv2. + OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets +-cat ext1_v4.expected | cut -c -48 > expout +-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c -48"]) + # Skipping the IPv4 checksum. +-cat ext1_v4.expected | cut -c 53- > expout +-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c 53-"]) + + # ovs-ofctl also resumes the packets and this causes other ports to receive + # the DHCP request packet. So reset the pcap files so that its easier to test. +@@ -20104,13 +20003,9 @@ OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`]) + # NXT_RESUMEs should be 2 in hv2. + OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \ +-sort > ext1_v6.packets +-cat ext1_v6.expected | cut -c -120 > expout +-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c -120"]) + # Skipping the UDP checksum +-cat ext1_v6.expected | cut -c 125- > expout +-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c 125-"]) + + rm -f ext1_v6.expected + rm -f ext1_v6.packets +@@ -20158,12 +20053,9 @@ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) + # NXT_RESUMEs should be 1 in hv3. + OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv3.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets +-cat ext1_v4.expected | cut -c -48 > expout +-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c -48"]) + # Skipping the IPv4 checksum. +-cat ext1_v4.expected | cut -c 53- > expout +-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c 53-"]) + + # ovs-ofctl also resumes the packets and this causes other ports to receive + # the DHCP request packet. So reset the pcap files so that its easier to test. +@@ -20188,13 +20080,9 @@ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) + # NXT_RESUMEs should be 2 in hv3. + OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv3.log | grep -c NXT_RESUME`]) + +-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \ +-sort > ext1_v6.packets +-cat ext1_v6.expected | cut -c -120 > expout +-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c -120"]) + # Skipping the UDP checksum +-cat ext1_v6.expected | cut -c 125- > expout +-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout]) ++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c 125-"]) + + # disconnect hv3 from the network, hv1 should take over + as hv3 +@@ -20568,12 +20456,12 @@ test_ip_packet_larger() { + expected=${expected}0000000000000000000000000000 + echo $expected > br_phys_n1.expected + else +- src_ip=`ip_to_hex 10 0 0 1` ++ src_ip=`ip_to_hex 172.168.0.100` + dst_ip=`ip_to_hex 10 0 0 3` + # pkt len should be 146 (28 (icmp packet) + 118 (orig ip + payload)) + reply_pkt_len=008e + ip_csum=fc97 +- icmp_reply=${src_mac}${dst_mac}08004500${reply_pkt_len}00004000fe01686b ++ icmp_reply=${src_mac}${dst_mac}08004500${reply_pkt_len}00004000fe01c55f + icmp_reply=${icmp_reply}${src_ip}${dst_ip}0304${ip_csum}0000$(printf "%04x" $mtu) + icmp_reply=${icmp_reply}4500${pkt_len}000000003f01c4dd + icmp_reply=${icmp_reply}${orig_packet_l3} +@@ -20665,7 +20553,7 @@ test_ip6_packet_larger() { + + local ipv6_src=10000000000000000000000000000003 + local ipv6_dst=20000000000000000000000000000002 +- local ipv6_rt=10000000000000000000000000000001 ++ local ipv6_rt=20000000000000000000000000000001 + + local payload=0000000000000000000000000000000000000000 + local payload=${payload}0000000000000000000000000000000000000000 +@@ -21223,6 +21111,7 @@ OVN_FOR_EACH_NORTHD([ + AT_SETUP([ipam to non-ipam]) + ovn_start + ++check ovn-nbctl --wait=sb sync + ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00" + ovn-nbctl ls-add sw0 + ovn-nbctl lsp-add sw0 p0 -- lsp-set-addresses p0 dynamic +@@ -23426,7 +23315,7 @@ check ovn-nbctl set Logical_Switch sw2 \ + other_config:mcast_querier="false" + + # Enable multicast snooping on sw3. +-check ovn-nbctl --wait=sb set Logical_Switch sw3 \ ++check ovn-nbctl --wait=hv set Logical_Switch sw3 \ + other_config:mcast_querier="false" \ + other_config:mcast_snoop="true" + +@@ -23465,7 +23354,7 @@ OVS_WAIT_UNTIL( + [$at_diff -F'^---' exp rcv]) + + # Enable multicast relay on rtr +-check ovn-nbctl --wait=sb set logical_router rtr options:mcast_relay="true" ++check ovn-nbctl --wait=hv set logical_router rtr options:mcast_relay="true" + + AT_CAPTURE_FILE([sbflows6]) + ovn-sbctl dump-flows > sbflows6 +@@ -26413,10 +26302,13 @@ check ovn-nbctl lsp-add ts ts-lr3 \ + wait_for_ports_up + + ovn_as az1 ++OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr2]) + check ovn-nbctl lsp-set-options ts-lr2 requested-chassis=hv2 ++OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr3]) + check ovn-nbctl lsp-set-options ts-lr3 requested-chassis=hv2 + + ovn_as az2 ++OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr1]) + check ovn-nbctl lsp-set-options ts-lr1 requested-chassis=hv1 + + dnl Enable unregistered IP multicast flooding and IP multicast relay. +@@ -26625,10 +26517,13 @@ check ovn-nbctl lsp-add ts ts-lr3 \ + wait_for_ports_up + + ovn_as az1 ++OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr2]) + check ovn-nbctl lsp-set-options ts-lr2 requested-chassis=hv2 ++OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr3]) + check ovn-nbctl lsp-set-options ts-lr3 requested-chassis=hv2 + + ovn_as az2 ++OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr1]) + check ovn-nbctl lsp-set-options ts-lr1 requested-chassis=hv1 + + dnl Enable IP multicast snooping and IP multicast relay. Reports are +@@ -27984,8 +27879,9 @@ ovn-nbctl ls-add ls1 + ovn-nbctl --wait=sb lsp-add ls1 lsp1 + + # Simulate the fact that lsp1 had been previously bound on hv1. +-ovn-sbctl --id=@e create encap chassis_name=hv1 ip="192.168.0.1" type="geneve" \ +- -- --id=@c create chassis name=hv1 encaps=@e \ ++ovn-sbctl --id=@e1 create encap chassis_name=hv1 ip="192.168.0.1" type="geneve" \ ++ --id=@e2 create encap chassis_name=hv1 ip="192.168.0.1" type="vxlan" \ ++ -- --id=@c create chassis name=hv1 encaps=@e1,@e2 \ + -- set Port_Binding lsp1 chassis=@c + + as hv1 +@@ -28011,8 +27907,9 @@ ovn-nbctl ls-add ls1 + ovn-nbctl --wait=sb lsp-add ls1 lsp1 + + # Simulate the fact that lsp1 had been previously bound on hv1. +-ovn-sbctl --id=@e create encap chassis_name=hv1 ip="192.168.0.1" type="geneve" \ +- -- --id=@c create chassis hostname=hv1 name=hv1 encaps=@e \ ++ovn-sbctl --id=@e1 create encap chassis_name=hv1 ip="192.168.0.1" type="geneve" \ ++ --id=@e2 create encap chassis_name=hv1 ip="192.168.0.1" type="vxlan" \ ++ -- --id=@c create chassis name=hv1 encaps=@e1,@e2 \ + -- set Port_Binding lsp1 chassis=@c + + as hv1 +@@ -28137,9 +28034,10 @@ OVS_WAIT_UNTIL([ + ]) + + as hv1 check ovs-ofctl del-flows br-phys ++ + AT_DATA([flows.txt], [dnl +-table=0, priority=0 actions=NORMAL + table=0, priority=200 arp,actions=drop ++table=0, priority=0 actions=NORMAL + table=0, priority=100, pkt_mark=0x64 actions=drop + table=0, priority=100, pkt_mark=0x2 actions=drop + table=0, priority=100, pkt_mark=0x3 actions=drop +@@ -30785,7 +30683,6 @@ check ovn-nbctl --wait=hv ls-lb-del sw0 lb-ipv6 + # original destination tuple. + # + # ovn-controller should fall back to matching on ct_nw_dst()/ct_tp_dst(). +-as northd-backup ovn-appctl -t NORTHD_TYPE pause + as northd ovn-appctl -t NORTHD_TYPE pause + + check ovn-sbctl \ +@@ -30836,7 +30733,6 @@ OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_a + + # Resume ovn-northd. + as northd ovn-appctl -t NORTHD_TYPE resume +-as northd-backup ovn-appctl -t NORTHD_TYPE resume + check ovn-nbctl --wait=hv sync + + as hv2 ovs-vsctl del-port hv2-vif1 +@@ -30957,9 +30853,6 @@ AT_CHECK([grep -c $northd_version hv1/ovn-controller.log], [0], [1 + as northd + OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) + +-as northd-backup +-OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) +- + check ovn-sbctl set SB_Global . options:northd_internal_version=foo + + as hv1 +@@ -31101,7 +30994,6 @@ AS_BOX([ovn-controller should not reset Port_Binding.up without northd]) + # Pause northd and clear the "up" field to simulate older ovn-northd + # versions writing to the Southbound DB. + as northd ovn-appctl -t NORTHD_TYPE pause +-as northd-backup ovn-appctl -t NORTHD_TYPE pause + + as hv1 ovn-appctl -t ovn-controller debug/pause + check ovn-sbctl clear Port_Binding lsp1 up +@@ -31117,7 +31009,6 @@ check_column "" Port_Binding up logical_port=lsp1 + # Once northd should explicitly set the Port_Binding.up field to 'false' and + # ovn-controller sets it to 'true' as soon as the update is processed. + as northd ovn-appctl -t NORTHD_TYPE resume +-as northd-backup ovn-appctl -t NORTHD_TYPE resume + wait_column "true" Port_Binding up logical_port=lsp1 + wait_column "true" nb:Logical_Switch_Port up name=lsp1 + +@@ -31272,7 +31163,6 @@ check ovn-nbctl lsp-set-addresses sw0-p4 "00:00:00:00:00:04 192.168.47.4" + # will be sent in one transaction. + + check as northd ovn-appctl -t NORTHD_TYPE pause +-check as northd-backup ovn-appctl -t NORTHD_TYPE pause + + check ovn-nbctl lsp-add sw0 sw0-p1 + check ovn-nbctl lsp-set-addresses sw0-p1 "00:00:00:00:00:01 192.168.47.1" +@@ -31453,10 +31343,6 @@ send_icmp_packet() { + as hv$hv ovs-appctl netdev-dummy/receive hv$hv-vif$inport $packet + } + +-trim_zeros() { +- sed 's/\(00\)\{1,\}$//' +-} +- + AS_BOX([Wait for all ports to be up]) + wait_for_ports_up + +@@ -33727,7 +33613,6 @@ check as hv1 ovn-appctl -t ovn-controller exit + + # Pause northd to guarantee that ovn-controller starts before requested_chassis + # column is filled. +-check as northd-backup ovn-appctl -t ovn-northd pause + check as northd ovn-appctl -t ovn-northd pause + + # Wait until requested_chassis is empty +@@ -33741,7 +33626,6 @@ wait_column "hv1" Chassis name + + # Start northd and wait for events to be processed + check as northd ovn-appctl -t ovn-northd resume +-check as northd-backup ovn-appctl -t ovn-northd resume + + wait_for_ports_up lsp1 + +@@ -34471,7 +34355,7 @@ dnat_zone=$(ovs-ofctl dump-flows br-int table=$DNAT_TABLE,metadata=0x${lr0_dp_ke + if test -n "$dnat_zone"; then + dnat_zone=${dnat_zone::-1} + fi +-snat_zone=$(ovs-ofctl dump-flows br-int table=$SNAT_TABLE,metadata=0x${lr0_dp_key} | grep priority=153 | grep -o zone=.*, | cut -d '=' -f 2) ++snat_zone=$(ovs-ofctl dump-flows br-int table=$SNAT_TABLE,metadata=0x${lr0_dp_key} | grep priority=153 | grep ct_state=-trk | grep -o zone=.*, | cut -d '=' -f 2) + if test -n "$snat_zone"; then + snat_zone=${snat_zone::-1} + fi +@@ -34488,7 +34372,7 @@ dnat_zone=$(ovs-ofctl dump-flows br-int table=$DNAT_TABLE,metadata=0x${lr0_dp_ke + if test -n "$dnat_zone"; then + dnat_zone=${dnat_zone::-1} + fi +-snat_zone=$(ovs-ofctl dump-flows br-int table=$SNAT_TABLE,metadata=0x${lr0_dp_key} | grep priority=153 | grep -o zone=.*, | cut -d '=' -f 2) ++snat_zone=$(ovs-ofctl dump-flows br-int table=$SNAT_TABLE,metadata=0x${lr0_dp_key} | grep priority=153 | grep ct_state=-trk | grep -o zone=.*, | cut -d '=' -f 2) + if test -n "$snat_zone"; then + snat_zone=${snat_zone::-1} + fi +@@ -34572,6 +34456,7 @@ AT_CLEANUP + + OVN_FOR_EACH_NORTHD([ + AT_SETUP([MAC binding aging]) ++AT_SKIP_IF([test $HAVE_SCAPY = no]) + ovn_start + + net_add n1 +@@ -35389,37 +35274,6 @@ OVN_CLEANUP([hv1],[hv2]) + AT_CLEANUP + ]) + +-OVN_FOR_EACH_NORTHD([ +-AT_SETUP([feature inactivity probe]) +-ovn_start +-net_add n1 +- +-sim_add hv1 +-as hv1 +-check ovs-vsctl add-br br-phys +-ovn_attach n1 br-phys 192.168.0.1 +- +-dnl Ensure that there are 4 openflow connections. +-OVS_WAIT_UNTIL([test "$(grep -c 'negotiated OpenFlow version' hv1/ovs-vswitchd.log)" -eq "4"]) +- +-dnl "Wait" 3 times 60 seconds and ensure ovn-controller writes to the +-dnl openflow connections in the meantime. This should allow ovs-vswitchd +-dnl to probe the openflow connections at least twice. +- +-as hv1 ovs-appctl time/warp 60000 +-check ovn-nbctl --wait=hv sync +- +-as hv1 ovs-appctl time/warp 60000 +-check ovn-nbctl --wait=hv sync +- +-as hv1 ovs-appctl time/warp 60000 +-check ovn-nbctl --wait=hv sync +- +-AT_CHECK([test -z "`grep disconnecting hv1/ovs-vswitchd.log`"]) +-OVN_CLEANUP([hv1]) +-AT_CLEANUP +-]) +- + OVN_FOR_EACH_NORTHD([ + AT_SETUP([Logical flows with Chassis_Template_Var references]) + AT_KEYWORDS([templates]) +@@ -35795,7 +35649,7 @@ as hv1 ovs-vsctl set-ssl \ + echo hv3 > ${OVN_SYSCONFDIR}/test_hv + + # the last argument is passed to ovn-controller through cli +-ovn_attach n1 br-phys 192.168.0.1 24 vxlan hv1 -n hv3 ++ovn_attach n1 br-phys 192.168.0.1 24 vxlan hv1 hv3 + + sim_add hv2 + as hv2 +@@ -36728,7 +36582,6 @@ check ovn-nbctl lsp-add ls2 public2 + check ovn-nbctl lsp-set-addresses public2 unknown + check ovn-nbctl lsp-set-type public2 localnet + check ovn-nbctl --wait=sb set Logical_Switch_Port public2 options:qos_min_rate=6000000000 options:qos_max_rate=7000000000 options:qos_burst=8000000000 options:network_name=phys +-check ovn-nbctl --wait=sb lsp-set-options public2 qos_min_rate=6000000000 qos_max_rate=7000000000 qos_burst=8000000000 + + # Let's now send ovn controller to sleep, so it will receive both ofport notification and ls deletion simultaneously + sleep_controller hv-1 +@@ -36861,14 +36714,14 @@ check ovn-nbctl ls-add sw0 + check ovn-nbctl lsp-add sw0 sw0-port1 + check ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:01 192.168.0.2" + ++ovs-vsctl add-port br-int p1 -- \ ++ set Interface p1 external_ids:iface-id=sw0-port1 ++ + check ovn-nbctl lsp-add sw0 sw0-lr0 + check ovn-nbctl lsp-set-type sw0-lr0 router + check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01 + check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 + +-ovs-vsctl add-port br-int p1 -- \ +- set Interface p1 external_ids:iface-id=sw0-port1 +- + check ovn-appctl -t ovn-controller vlog/set dbg:main + + wait_for_ports_up +@@ -37005,3 +36858,548 @@ AT_CHECK([grep -c "NXT_CT_FLUSH_ZONE" hv1/ovs-vswitchd.log], [0], [dnl + OVN_CLEANUP([hv1]) + AT_CLEANUP + ]) ++ ++OVN_FOR_EACH_NORTHD([ ++AT_SETUP([virtual port claim race condition]) ++AT_KEYWORDS([virtual ports]) ++ovn_start ++ ++send_garp() { ++ local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6 ++ local request=${eth_dst}${eth_src}08060001080006040001${eth_src}${spa}${eth_dst}${tpa} ++ as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request ++} ++ ++net_add n1 ++ ++sim_add hv1 ++as hv1 ++ovs-vsctl add-br br-phys ++ovn_attach n1 br-phys 192.168.0.1 ++ovn-appctl vlog/set dbg ++ovs-vsctl -- add-port br-int hv1-vif1 -- \ ++ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \ ++ options:tx_pcap=hv1/vif1-tx.pcap \ ++ options:rxq_pcap=hv1/vif1-rx.pcap \ ++ ofport-request=1 ++ovs-vsctl -- add-port br-int hv1-vif3 -- \ ++ set interface hv1-vif3 \ ++ options:tx_pcap=hv1/vif3-tx.pcap \ ++ options:rxq_pcap=hv1/vif3-rx.pcap \ ++ ofport-request=3 ++ ++ovs-appctl -t ovn-controller vlog/set dbg ++ ++ovn-nbctl ls-add sw0 ++ ++check ovn-nbctl lsp-add sw0 sw0-vir ++check ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10" ++check ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10" ++check ovn-nbctl lsp-set-type sw0-vir virtual ++check ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10 ++check ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1 ++ ++check ovn-nbctl lsp-add sw0 sw0-p1 ++check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3 1000::3" ++check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3 10.0.0.10 1000::3" ++ ++# Create a logical router and attach both logical switches ++check ovn-nbctl lr-add lr0 ++check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 1000::a/64 ++check ovn-nbctl lsp-add sw0 sw0-lr0 ++check ovn-nbctl lsp-set-type sw0-lr0 router ++check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01 ++check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 ++ ++wait_for_ports_up ++ovn-nbctl --wait=hv sync ++hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"` ++ ++# Try to bind sw0-vir directly to an OVS port. This should be ignored by ++# ovn-controller. ++as hv1 ovs-vsctl set interface hv1-vif3 external-ids:iface-id=sw0-vir ++ ++# Make sb to sleep, so that claim of sw0-vir (through pinctrl) and hv1-vif3 can be handled within same idl by controller ++sleep_sb ++ ++# From sw0-p0 send GARP for 10.0.0.10. hv1 should claim sw0-vir ++# and sw0-p1 should be its virtual_parent. ++eth_src=505400000003 ++eth_dst=ffffffffffff ++spa=$(ip_to_hex 10 0 0 10) ++tpa=$(ip_to_hex 10 0 0 10) ++send_garp 1 1 $eth_src $eth_dst $spa $tpa ++ ++OVS_WAIT_UNTIL([test 1 = `cat hv1/ovn-controller.log | grep "pinctrl received packet-in" | \ ++grep opcode=BIND_VPORT | grep OF_Table_ID=29 | wc -l`]) ++ ++sleep_controller hv1 ++ ++# Cleanup hv1-vif3. This should not interfere with sw0-vir claim ++as hv1 ovs-vsctl del-port hv1-vif3 ++ ++wake_up_sb ++ovn-nbctl --wait=sb sync ++wake_up_controller hv1 ++check ovn-nbctl --wait=hv sync ++ ++wait_row_count Port_Binding 1 logical_port=sw0-vir chassis=$hv1_ch_uuid ++check_row_count Port_Binding 1 logical_port=sw0-vir virtual_parent=sw0-p1 ++wait_for_ports_up sw0-vir ++check ovn-nbctl --wait=hv sync ++ ++OVN_CLEANUP([hv1]) ++AT_CLEANUP ++]) ++ ++OVN_FOR_EACH_NORTHD([ ++AT_SETUP([pod to pod with localnet_learn_fdb]) ++AT_SKIP_IF([test $HAVE_SCAPY = no]) ++ ++# 6 VIFs, 3 per HV: vif11, vif12, vif13 on hv1. ++# vif11 will exchange packets with vif21, vif12 w/ vif22 and so on. ++# ++ovn_start ++net_add n1 ++ ++check ovn-nbctl ls-add ls0 ++ ++check ovn-nbctl lsp-add ls0 ln_port -- \ ++ lsp-set-addresses ln_port unknown -- \ ++ lsp-set-type ln_port localnet -- \ ++ lsp-set-options ln_port network_name=physnet1 ++ ++for i in $(seq 1 3); do ++ check ovn-nbctl lsp-add ls0 vif1$i -- \ ++ lsp-set-addresses vif1$i unknown ++ check ovn-nbctl lsp-add ls0 vif2$i -- \ ++ lsp-set-addresses vif2$i unknown ++done ++ ++n_pkt=(0 0 0 0 0 0) ++ ++for hv in 1 2; do ++ sim_add hv${hv} ++ as hv${hv} ++ ovs-vsctl add-br br-phys ++ ovn_attach n1 br-phys 192.168.0.${hv} ++ ++ for i in $(seq 1 3); do ++ ovs-vsctl -- add-port br-int vif${hv}${i} -- \ ++ set interface vif${hv}${i} external-ids:iface-id=vif${hv}${i} \ ++ options:tx_pcap=hv${hv}/vif${hv}${i}-tx.pcap \ ++ options:rxq_pcap=hv${hv}/vif${hv}${i}-rx.pcap \ ++ ofport-request=$i ++ done ++ ovs-vsctl -- add-port br-phys ext0 -- \ ++ set interface ext0 \ ++ options:tx_pcap=hv${hv}/ext0-tx.pcap \ ++ options:rxq_pcap=hv${hv}/ext0-rx.pcap \ ++ ofport-request=4 ++ ovs-vsctl set open . external_ids:ovn-bridge-mappings=physnet1:br-phys ++done ++ ++OVN_POPULATE_ARP ++wait_for_ports_up ++check ovn-nbctl --wait=hv sync ++ ++ln_port_key=$(fetch_column port_binding tunnel_key logical_port=ln_port) ++vif11_key=$(fetch_column port_binding tunnel_key logical_port=vif11) ++vif12_key=$(fetch_column port_binding tunnel_key logical_port=vif12) ++vif13_key=$(fetch_column port_binding tunnel_key logical_port=vif13) ++vif21_key=$(fetch_column port_binding tunnel_key logical_port=vif21) ++vif22_key=$(fetch_column port_binding tunnel_key logical_port=vif22) ++vif23_key=$(fetch_column port_binding tunnel_key logical_port=vif23) ++ ++ensure_controller_run() { ++# We want to make sure controller could run at least one full loop. ++# We can't use wait=hv as sb might be sleeping. ++# Use 2 ovn-appctl to guarentee that ovn-controller run the full loop, and not just the unixctl handling ++ hv=$1 ++ OVS_WAIT_UNTIL([test x$(as $hv ovn-appctl -t ovn-controller debug/status) = "xrunning"]) ++ OVS_WAIT_UNTIL([test x$(as $hv ovn-appctl -t ovn-controller debug/status) = "xrunning"]) ++} ++ ++wait_for_packets() { ++ local hv=$1 ++ local vif=$2 ++ counter=$(((${hv:2} - 1) * 3 + ${vif:4} - 1)) ++ n_pkt[[$counter]]=$((${n_pkt[[$counter]]} + 1)) ++ echo "waiting for ${n_pkt[[$counter]]} packets on ${hv}/${vif} using counter $counter" ++ OVS_WAIT_UNTIL([test $(tcpdump ip -nnner ${hv}/${vif}-tx.pcap | wc -l) -eq ${n_pkt[[$counter]]}]) ++} ++ ++check_no_packets() { ++ local hv=$1 ++ local vif=$2 ++ counter=$(((${hv:2} - 1) * 3 + ${vif:4} - 1)) ++ echo "waiting for ${n_pkt[[$counter]]} packets on ${hv}/${vif} using counter $counter" ++ OVS_WAIT_UNTIL([test $(tcpdump ip -nnner ${hv}/${vif}-tx.pcap | wc -l) -eq ${n_pkt[[$counter]]}]) ++} ++ ++send_packet() { ++ hv=$1 ++ a_src=$2 ++ a_dst=$3 ++ dev=vif$2 ++ AS_BOX([$(date +%H:%M:%S.%03N) sending packet from $dev in $hv $a_src to $a_dst]) ++ packet=$(fmt_pkt " ++ Ether(dst='00:00:00:00:10:${a_dst}', src='00:00:00:00:10:${a_src}') / ++ IP(src='192.168.10.${a_src}', dst='192.168.10.${a_dst}') / ++ UDP(sport=1234, dport=1235) ++ ") ++ as $hv ovs-appctl netdev-dummy/receive $dev $packet ++} ++ ++check_flow_count() { ++ hv=$1 ++ count=$2 ++ echo "Checking flow count for $hv is $count" ++ OVS_WAIT_UNTIL([test $count = $(as $hv ovs-ofctl dump-flows br-int table=72 | grep -v "NXST_FLOW reply" | wc -l)]) ++} ++ ++# Sending packet in both direction. Should create FDB entries for vifs ++# No localnet_learn_fdb yet ++AS_BOX([$(date +%H:%M:%S.%03N) vif11 <=> vif21]) ++send_packet hv1 11 21 ++for i in 1 2 3; do ++ wait_for_packets hv2 vif2${i} ++done ++for i in 2 3; do ++ wait_for_packets hv1 vif1${i} ++done ++# vif11 should now own the mac ++wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"' ++ ++send_packet hv2 21 11 ++wait_for_packets hv1 vif11 ++for i in 2 3; do ++ check_no_packets hv1 vif1${i} ++done ++wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"' ++ ++check_flow_count hv1 2 ++check_flow_count hv2 2 ++ ++# We now enable localnet_learn_fdb ++# We check how it behaves with existing vif entries in fdb ++check ovn-nbctl --wait=hv set logical_switch_port ln_port options:localnet_learn_fdb=true ++ ++AS_BOX([$(date +%H:%M:%S.%03N) vif11 <=> vif21 after learn_fdb]) ++send_packet hv1 11 21 ++ ++wait_for_packets hv2 vif21 ++for i in 2 3; do ++ check_no_packets hv2 vif2${i} ++done ++wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"' ++ ++send_packet hv2 21 11 ++wait_for_packets hv1 vif11 ++for i in 2 3; do ++ check_no_packets hv1 vif1${i} ++done ++wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"' ++ ++# In both controllers, ++# - 1st packet (in both dir) should have reached controller for the vif, not for localnet (as learn_fdb disabled for localnet) ++# - 2nd packet should not cause any upcall to controller as vif already owns the mac. ++AT_CHECK([test 1 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`]) ++AT_CHECK([test 1 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`]) ++ ++check_flow_count hv1 4 ++check_flow_count hv2 4 ++ ++# Send a few more packets ++send_packet hv1 11 21 ++send_packet hv2 21 11 ++ ++for hv in 1 2; do ++ wait_for_packets hv${hv} vif${hv}1 ++ for i in 2 3; do ++ echo CHECK ++ check_no_packets hv${hv} vif${hv}${i} ++ done ++done ++ ++# The last packets should have gone through the fast path ++AT_CHECK([test 1 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`]) ++AT_CHECK([test 1 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`]) ++ ++# Check that there are no bad surprises ++wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"' ++wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"' ++ ++# We will now create fdb entries AFTER enabing localnet_learn_fdb ++# We make ovn_controller (hv1 or hv2) to sleep to control who writes first to fdb ++# as otherwise no guarentee. ++AS_BOX([$(date +%H:%M:%S.%03N) vif12 <=> vif22]) ++# We make sure that the fdb update by the vif is done after the localnet update ++sleep_controller hv1 ++send_packet hv1 12 22 ++for i in 1 3; do ++ wait_for_packets hv1 vif1${i} ++done ++for i in 1 2 3; do ++ wait_for_packets hv2 vif2${i} ++done ++ ++# ln_port should own the mac as vif not written yet ++wait_column "$ln_port_key" fdb port_key mac='"00:00:00:00:10:12"' ++ ++wake_up_controller hv1 ++# vif1 should now own the mac ++wait_column "$vif12_key" fdb port_key mac='"00:00:00:00:10:12"' ++ ++sleep_controller hv2 ++send_packet hv2 22 12 ++wait_for_packets hv1 vif12 ++for i in 1 3; do ++ check_no_packets hv1 vif1${i} ++done ++wait_column "$ln_port_key" fdb port_key mac='"00:00:00:00:10:22"' ++ ++wake_up_controller hv2 ++wait_column "$vif22_key" fdb port_key mac='"00:00:00:00:10:22"' ++ ++check_flow_count hv1 8 ++check_flow_count hv2 8 ++ ++AS_BOX([$(date +%H:%M:%S.%03N) vif13 <=> vif23 ]) ++# Now we do the other way around: we make sure that the localnet update is done after the vif update. ++# So, when packet is sent from vif1 to vif2, vif1 will be learnt (by hv1) and written in sb ++# Then, when we wake up ovn-controller on hv2, it will learn on localnet. ++# This used to cause localnet entry to overwrite vif entry in sb ++sleep_controller hv2 ++send_packet hv1 13 23 ++for i in 1 2; do ++ wait_for_packets hv1 vif1${i} ++done ++for i in 1 2 3; do ++ wait_for_packets hv2 vif2${i} ++done ++ ++wait_column "$vif13_key" fdb port_key mac='"00:00:00:00:10:13"' ++ ++ ++# At this point, FDB contains vif1 entry for mac 00:00:00:00:10:13. ++# However, as hv2 controller is sleeping, the flows in hv2 do not ++# contain the flows related to that fdb entry. ++# Hence, the packet which went through still failed the lookup. ++wake_up_controller hv2 ++ ++# FDB shoud not have changed. Just make sure controller has run and check fdb ++ensure_controller_run hv2 ++wait_column "$vif13_key" fdb port_key mac='"00:00:00:00:10:13"' ++ ++sleep_controller hv1 ++send_packet hv2 23 13 ++wait_for_packets hv1 vif13 ++for i in 1 2; do ++ check_no_packets hv2 vif2${i} ++done ++for i in 1 2; do ++ check_no_packets hv1 vif1${i} ++done ++wait_column "$vif23_key" fdb port_key mac='"00:00:00:00:10:23"' ++ ++wake_up_controller hv1 ++ensure_controller_run hv1 ++wait_column "$vif23_key" fdb port_key mac='"00:00:00:00:10:23"' ++ ++# In both controllers ++# - vif11 <=> vif21: 1 PACKET_IN ++# - vif12 <=> vif22: 2 PACKET_IN ++# - vif13 <=> vif23: 2 PACKET_IN ++# controller + . ++AT_CHECK([test 5 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`]) ++AT_CHECK([test 5 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`]) ++ ++# Send a few more packets ++send_packet hv1 13 23 ++send_packet hv2 23 13 ++wait_for_packets hv2 vif23 ++wait_for_packets hv1 vif13 ++for i in 1 2; do ++ check_no_packets hv1 vif1${i} ++ check_no_packets hv2 vif2${i} ++done ++send_packet hv1 13 23 ++send_packet hv2 23 13 ++wait_for_packets hv2 vif23 ++wait_for_packets hv1 vif13 ++for i in 1 2; do ++ check_no_packets hv1 vif1${i} ++ check_no_packets hv2 vif2${i} ++done ++ ++# The last packets should have gone through the fast path ++AT_CHECK([test 5 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`]) ++AT_CHECK([test 5 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`]) ++ ++# Check that there are no bad surprises ++wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"' ++wait_column "$vif12_key" fdb port_key mac='"00:00:00:00:10:12"' ++wait_column "$vif13_key" fdb port_key mac='"00:00:00:00:10:13"' ++wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"' ++wait_column "$vif22_key" fdb port_key mac='"00:00:00:00:10:22"' ++wait_column "$vif23_key" fdb port_key mac='"00:00:00:00:10:23"' ++ ++check_flow_count hv1 12 ++check_flow_count hv2 12 ++ ++OVN_CLEANUP([hv1]) ++AT_CLEANUP ++]) ++ ++OVN_FOR_EACH_NORTHD_NO_HV([ ++AT_SETUP([port up with slow northd]) ++ovn_start ++ ++sleep_northd() { ++ echo northd going to sleep ++ AT_CHECK([kill -STOP $(cat northd/ovn-northd.pid)]) ++} ++ ++wake_up_northd() { ++ echo northd going to sleep ++ AT_CHECK([kill -CONT $(cat northd/ovn-northd.pid)]) ++} ++ ++net_add n1 ++sim_add hv1 ++as hv1 ++ovs-vsctl add-br br-phys ++ovn_attach n1 br-phys 192.168.0.11 ++ ++check ovn-nbctl --wait=hv ls-add ls0 ++# Create a pilot port and wait it up to make sure we are ready for the real ++# tests, so that the counters measured are accurate. ++check ovn-nbctl --wait=hv lsp-add ls0 lsp-pilot -- lsp-set-addresses lsp-pilot "unknown" ++ovs-vsctl add-port br-int lsp-pilot -- set interface lsp-pilot external_ids:iface-id=lsp-pilot ++wait_for_ports_up ++check ovn-nbctl --wait=hv sync ++ ++check ovn-nbctl --wait=hv lsp-add ls0 lsp0-2 -- lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:02 192.168.0.12" ++ovs-vsctl add-port br-int lsp0-2 -- set interface lsp0-2 external_ids:iface-id=lsp0-2 ++wait_for_ports_up ++check ovn-nbctl --wait=hv sync ++ ++sleep_northd ++check ovn-nbctl lsp-del lsp0-2 ++check ovn-nbctl lsp-add ls0 lsp0-2 -- lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:02 192.168.0.12" ++wake_up_northd ++ ++check ovn-nbctl --wait=sb sync ++wait_for_ports_up ++ ++OVN_CLEANUP([hv1]) ++AT_CLEANUP ++]) ++ ++OVN_FOR_EACH_NORTHD([ ++AT_SETUP([gratuitous arp max timeout]) ++AT_KEYWORDS([slowtest]) ++TAG_UNSTABLE ++AT_SKIP_IF([test $HAVE_TCPDUMP = no]) ++ovn_start ++ ++check ovn-nbctl ls-add ls0 ++check ovn-nbctl lr-add lr0 ++check ovn-nbctl lrp-add lr0 lr0-ls0 f0:00:00:00:00:01 192.168.0.1/24 ++check ovn-nbctl lsp-add ls0 ls0-lr0 -- set Logical_Switch_Port ls0-lr0 \ ++ type=router options:router-port=lr0-ls0 addresses='"f0:00:00:00:00:01"' ++ ++check ovn-nbctl lsp-add ls0 ln_port ++check ovn-nbctl lsp-set-addresses ln_port unknown ++check ovn-nbctl lsp-set-type ln_port localnet ++check ovn-nbctl --wait=hv lsp-set-options ln_port network_name=physnet1 ++ ++net_add n1 ++sim_add hv1 ++as hv1 ++check ovs-vsctl \ ++ -- add-br br-phys \ ++ -- add-br br-eth0 ++ ++ovn_attach n1 br-phys 192.168.0.10 ++ ++check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0 ++check ovs-vsctl add-port br-eth0 snoopvif -- set Interface snoopvif options:tx_pcap=hv1/snoopvif-tx.pcap options:rxq_pcap=hv1/snoopvif-rx.pcap ++ ++# set garp max timeout to 2s ++as hv1 check ovs-vsctl set Open_vSwitch . external-ids:garp-max-timeout-sec=2 ++ ++# Wait until the patch ports are created in hv1 to connect br-int to br-eth0 ++check ovn-nbctl set logical_router lr0 options:chassis=hv1 ++OVN_WAIT_PATCH_PORT_FLOWS(["ln_port"], ["hv1"]) ++ ++# sleep for 12s to get a garp every ~ 2s ++sleep 12 ++ ++n_arp=$(tcpdump -ner hv1/snoopvif-tx.pcap arp | wc -l) ++AT_CHECK([test $n_arp -ge 5 -a $n_arp -lt 10]) ++ ++# Temporarily remove lr0 chassis ++# Wait for hv confirmation to make sure chassis is removed before we reset pcap ++# Otherwise a garp might be sent after pcap have been reset but before chassis is removed ++check ovn-nbctl --wait=hv remove logical_router lr0 options chassis ++ ++as hv1 reset_pcap_file snoopvif hv1/snoopvif ++# set garp max timeout to 1s ++as hv1 check ovs-vsctl set Open_vSwitch . external-ids:garp-max-timeout-sec=1 ++check ovn-nbctl set logical_router lr0 options:chassis=hv1 ++ ++# sleep for 7s to get a garp every ~ 1s ++sleep 7 ++ ++n_arp=$(tcpdump -ner hv1/snoopvif-tx.pcap arp | wc -l) ++AT_CHECK([test $n_arp -ge 5 -a $n_arp -lt 10]) ++ ++OVN_CLEANUP([hv1]) ++ ++AT_CLEANUP ++]) ++ ++OVN_FOR_EACH_NORTHD([ ++AT_SETUP([Changing tunnel_key]) ++ovn_start ++ ++net_add n1 ++ ++sim_add hv1 ++as hv1 ++check ovs-vsctl add-br br-phys ++ovn_attach n1 br-phys 192.168.0.11 ++ ++check ovn-nbctl --wait=hv ls-add ls \ ++ -- lsp-add ls lsp1 \ ++ -- lsp-add ls ls-lr \ ++ -- lr-add lr \ ++ -- lrp-add lr lr-ls f0:00:00:00:00:f1 192.168.1.1/24 \ ++ -- set Logical_Switch_Port ls-lr \ ++ type=router \ ++ options:router-port=lr-ls \ ++ addresses=router \ ++ -- lrp-set-gateway-chassis lr-ls hv1 ++ ++sleep_controller hv1 ++ ++check ovn-nbctl --wait=sb set Logical_Switch ls other_config:requested-tnl-key=1000 ++check ovn-nbctl --wait=sb ls-del ls ++wake_up_controller hv1 ++ ++check ovn-nbctl --wait=hv sync ++ ++check ovn-nbctl --wait=hv ls-add ls1 \ ++ -- lsp-add ls1 ls1-lr \ ++ -- lrp-add lr lr-ls1 f0:00:00:00:00:f2 192.168.2.1/24 \ ++ -- set Logical_Switch_Port ls1-lr type=router options:router-port=lr-ls1 addresses=router \ ++ -- lrp-set-gateway-chassis lr-ls1 hv1 ++ ++check ovn-nbctl --wait=hv set Logical_Switch ls1 other_config:requested-tnl-key=1001 ++ ++OVN_CLEANUP([hv1]) ++ ++AT_CLEANUP ++]) +diff --git a/tests/scapy-server.py b/tests/scapy-server.py +new file mode 100755 +index 000000000..1cc616f70 +--- /dev/null ++++ b/tests/scapy-server.py +@@ -0,0 +1,81 @@ ++#!/usr/bin/env python3 ++ ++import argparse ++import time ++ ++import ovs.daemon ++import ovs.unixctl ++import ovs.unixctl.server ++ ++import binascii ++from scapy.all import * # noqa: F401,F403 ++from scapy.all import raw ++ ++ ++vlog = ovs.vlog.Vlog("scapy-server") ++exiting = False ++ ++ ++def exit(conn, argv, aux): ++ global exiting ++ ++ exiting = True ++ conn.reply(None) ++ ++ ++def process(data): ++ start_time = time.perf_counter() ++ vlog.info(f"received payload request: {data}") ++ try: ++ data = data.replace('\n', '') ++ return binascii.hexlify(raw(eval(data))).decode() ++ except Exception as e: ++ vlog.exception(f"failed to process payload request: {e}") ++ return "" ++ finally: ++ total_time = (time.perf_counter() - start_time) * 1000 ++ vlog.info(f"took {total_time:.2f}ms to process payload request") ++ ++ ++def payload(conn, argv, aux): ++ try: ++ conn.reply(process(argv[0])) ++ except Exception as e: ++ vlog.exception(f"failed to reply to payload request: {e}") ++ ++ ++def main(): ++ parser = argparse.ArgumentParser( ++ description="Scapy-based Frame Payload Generator") ++ parser.add_argument("--unixctl", help="UNIXCTL socket location or 'none'.") ++ ++ ovs.daemon.add_args(parser) ++ ovs.vlog.add_args(parser) ++ args = parser.parse_args() ++ ovs.daemon.handle_args(args) ++ ovs.vlog.handle_args(args) ++ ++ ovs.daemon.daemonize_start() ++ error, server = ovs.unixctl.server.UnixctlServer.create(args.unixctl) ++ if error: ++ ovs.util.ovs_fatal(error, "could not create unixctl server at %s" ++ % args.unixctl, vlog) ++ ++ ovs.unixctl.command_register("exit", "", 0, 0, exit, None) ++ ovs.unixctl.command_register("payload", "", 1, 1, payload, None) ++ ovs.daemon.daemonize_complete() ++ ++ vlog.info("scapy server ready") ++ ++ poller = ovs.poller.Poller() ++ while not exiting: ++ server.run() ++ server.wait(poller) ++ if exiting: ++ poller.immediate_wake() ++ poller.block() ++ server.close() ++ ++ ++if __name__ == '__main__': ++ main() +diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at +index 97c6e433b..65c4884ea 100644 +--- a/tests/system-common-macros.at ++++ b/tests/system-common-macros.at +@@ -372,7 +372,7 @@ ADD_VETH(sw11, sw11, br-int, "192.168.2.2/24", "f0:00:00:02:02:03", \ + "192.168.2.1") + ADD_NAMESPACES(server) + ADD_VETH(s1, server, br-ext, "2001:1db8:3333::2/64", "f0:00:00:01:02:05", \ +- "2001:1db8:3333::1") ++ "2001:1db8:3333::1", "nodad") + + if test X"$1" = X"GR"; then + ovn-nbctl create Logical_Router name=R1 options:chassis=hv1 +@@ -409,9 +409,6 @@ ovn-nbctl lsp-add sw0 sw01 \ + ovn-nbctl lsp-add sw1 sw11 \ + -- lsp-set-addresses sw11 "f0:00:00:02:02:03 192.168.2.2" + +-OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep 2001:1db8:3333::2 | grep tentative)" = ""]) +-OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep fe80 | grep tentative)" = ""]) +- + AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext]) + ovn-nbctl lsp-add public public1 \ + -- lsp-set-addresses public1 unknown \ +diff --git a/tests/system-ovn-kmod.at b/tests/system-ovn-kmod.at +index b29e6b55a..039d71170 100644 +--- a/tests/system-ovn-kmod.at ++++ b/tests/system-ovn-kmod.at +@@ -1,224 +1,5 @@ + AT_BANNER([system-ovn-kmod]) + +-# SCTP and userspace conntrack do not mix. Therefore this +-# test only can be run with kernel datapath. Otherwise, +-# this is mostly a copy of existing load balancer tests +-# in system-ovn.at +-AT_SETUP([load balancing in gateway router - SCTP]) +-AT_SKIP_IF([test $HAVE_SCTP = no]) +-AT_SKIP_IF([test $HAVE_NC = no]) +-AT_KEYWORDS([ovnlb sctp]) +- +-# Make sure the SCTP kernel module is loaded. +-LOAD_MODULE([sctp]) +- +-CHECK_CONNTRACK() +-CHECK_CONNTRACK_NAT() +-ovn_start +-OVS_TRAFFIC_VSWITCHD_START() +-ADD_BR([br-int]) +- +-# Set external-ids in br-int needed for ovn-controller +-ovs-vsctl \ +- -- set Open_vSwitch . external-ids:system-id=hv1 \ +- -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ +- -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ +- -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ +- -- set bridge br-int fail-mode=secure other-config:disable-in-band=true +- +-# Start ovn-controller +-start_daemon ovn-controller +- +-# Logical network: +-# Two LRs - R1 and R2 that are connected to each other via LS "join" +-# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and +-# bar (192.168.2.0/24) connected to it. R2 has alice (172.16.1.0/24) connected +-# to it. R2 is a gateway router on which we add load-balancing rules. +-# +-# foo -- R1 -- join - R2 -- alice +-# | +-# bar ---- +- +-ovn-nbctl create Logical_Router name=R1 +-ovn-nbctl create Logical_Router name=R2 options:chassis=hv1 +- +-ovn-nbctl ls-add foo +-ovn-nbctl ls-add bar +-ovn-nbctl ls-add alice +-ovn-nbctl ls-add join +- +-# Connect foo to R1 +-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 +-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ +- type=router options:router-port=foo addresses=\"00:00:01:01:02:03\" +- +-# Connect bar to R1 +-ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24 +-ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \ +- type=router options:router-port=bar addresses=\"00:00:01:01:02:04\" +- +-# Connect alice to R2 +-ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24 +-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ +- type=router options:router-port=alice addresses=\"00:00:02:01:02:03\" +- +-# Connect R1 to join +-ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24 +-ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \ +- type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"' +- +-# Connect R2 to join +-ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24 +-ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \ +- type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"' +- +-# Static routes. +-ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2 +-ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1 +- +-# Logical port 'foo1' in switch 'foo'. +-ADD_NAMESPACES(foo1) +-ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \ +- "192.168.1.1") +-ovn-nbctl lsp-add foo foo1 \ +--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" +- +-# Logical port 'alice1' in switch 'alice'. +-ADD_NAMESPACES(alice1) +-ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:04", \ +- "172.16.1.1") +-ovn-nbctl lsp-add alice alice1 \ +--- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2" +- +-# Logical port 'bar1' in switch 'bar'. +-ADD_NAMESPACES(bar1) +-ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \ +-"192.168.2.1") +-ovn-nbctl lsp-add bar bar1 \ +--- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2" +- +-# Config OVN load-balancer with a VIP. +-uuid=`ovn-nbctl create load_balancer protocol=sctp vips:30.0.0.1="192.168.1.2,192.168.2.2"` +-ovn-nbctl set logical_router R2 load_balancer=$uuid +- +-# Config OVN load-balancer with another VIP (this time with ports). +-ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:12345,192.168.2.2:12345"' +- +-# Add SNAT rule to make sure that Load-balancing still works with a SNAT rule. +-ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \ +- external_ip=30.0.0.2 -- add logical_router R2 nat @nat +- +-# Wait for ovn-controller to catch up. +-ovn-nbctl --wait=hv sync +-OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \ +-grep 'nat(dst=192.168.2.2:12345)']) +- +-# Start webservers in 'foo1', 'bar1'. +-OVS_START_L7([foo1], [sctp]) +-OVS_START_L7([bar1], [sctp]) +- +-on_exit "ovs-ofctl -O OpenFlow13 dump-flows br-int" +- +-dnl Should work with the virtual IP address through NAT +-for i in `seq 1 20`; do +- echo Request $i +- NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.1 12345 > client$i.log]) +-done +- +-dnl Each server should have at least one connection. +-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | +-sed -e 's/zone=[[0-9]]*/zone=/' | +-sed -e 's/vtag_orig=[[0-9]]*/vtag_orig=/' | +-sed -e 's/vtag_reply=[[0-9]]*/vtag_reply=/' | uniq], [0], [dnl +-sctp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=,dport=),reply=(src=192.168.1.2,dst=172.16.1.2,sport=,dport=),zone=,mark=2,protoinfo=(state=,vtag_orig=,vtag_reply=) +-sctp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=,dport=),reply=(src=192.168.2.2,dst=172.16.1.2,sport=,dport=),zone=,mark=2,protoinfo=(state=,vtag_orig=,vtag_reply=) +-]) +- +-dnl Test load-balancing that includes L4 ports in NAT. +-for i in `seq 1 20`; do +- echo Request $i +- NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.2 8000 > clients$i.log]) +-done +- +-dnl Each server should have at least one connection. +-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | +-sed -e 's/zone=[[0-9]]*/zone=/' | +-sed -e 's/vtag_orig=[[0-9]]*/vtag_orig=/' | +-sed -e 's/vtag_reply=[[0-9]]*/vtag_reply=/' | uniq], [0], [dnl +-sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.1.2,dst=172.16.1.2,sport=,dport=),zone=,mark=2,protoinfo=(state=,vtag_orig=,vtag_reply=) +-sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.2.2,dst=172.16.1.2,sport=,dport=),zone=,mark=2,protoinfo=(state=,vtag_orig=,vtag_reply=) +-]) +- +-check_est_flows () { +- n=$(ovs-ofctl dump-flows br-int table=15 | grep "+est" \ +- | grep "ct_mark=$1" | sed -n 's/.*n_packets=\([[0-9]]\{1,\}\).*/\1/p') +- +- echo "n_packets=$n" +- test -n "$n" && test "$n" != "0" +-} +- +-OVS_WAIT_UNTIL([check_est_flows 0x2], [check established flows]) +- +- +-ovn-nbctl set logical_router R2 options:lb_force_snat_ip="20.0.0.2" +- +-# Destroy the load balancer and create again. ovn-controller will +-# clear the OF flows and re add again and clears the n_packets +-# for these flows. +-ovn-nbctl destroy load_balancer $uuid +-uuid=`ovn-nbctl create load_balancer protocol=sctp vips:30.0.0.1="192.168.1.2,192.168.2.2"` +-ovn-nbctl set logical_router R2 load_balancer=$uuid +- +-check ovs-appctl dpctl/flush-conntrack +- +-# Config OVN load-balancer with another VIP (this time with ports). +-ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:12345,192.168.2.2:12345"' +- +-ovn-nbctl list load_balancer +-ovn-sbctl dump-flows R2 +-OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=45 | grep 'nat(src=20.0.0.2)']) +- +-dnl Test load-balancing that includes L4 ports in NAT. +-for i in `seq 1 20`; do +- echo Request $i +- NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.2 8000 > clients$i.log]) +-done +- +-dnl Each server should have at least one connection. +-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | +-sed -e 's/zone=[[0-9]]*/zone=/' | +-sed -e 's/vtag_orig=[[0-9]]*/vtag_orig=/' | +-sed -e 's/vtag_reply=[[0-9]]*/vtag_reply=/' | uniq], [0], [dnl +-sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.1.2,dst=172.16.1.2,sport=,dport=),zone=,mark=10,protoinfo=(state=,vtag_orig=,vtag_reply=) +-sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.2.2,dst=172.16.1.2,sport=,dport=),zone=,mark=10,protoinfo=(state=,vtag_orig=,vtag_reply=) +-]) +- +-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) | +-sed -e 's/zone=[[0-9]]*/zone=/' | +-sed -e 's/vtag_orig=[[0-9]]*/vtag_orig=/' | +-sed -e 's/vtag_reply=[[0-9]]*/vtag_reply=/' | uniq], [0], [dnl +-sctp,orig=(src=172.16.1.2,dst=192.168.1.2,sport=,dport=),reply=(src=192.168.1.2,dst=20.0.0.2,sport=,dport=),zone=,protoinfo=(state=,vtag_orig=,vtag_reply=) +-sctp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=,dport=),reply=(src=192.168.2.2,dst=20.0.0.2,sport=,dport=),zone=,protoinfo=(state=,vtag_orig=,vtag_reply=) +-]) +- +-OVS_WAIT_UNTIL([check_est_flows 0xa], [check established flows]) +- +-OVS_APP_EXIT_AND_WAIT([ovn-controller]) +- +-as ovn-sb +-OVS_APP_EXIT_AND_WAIT([ovsdb-server]) +- +-as ovn-nb +-OVS_APP_EXIT_AND_WAIT([ovsdb-server]) +- +-as northd +-OVS_APP_EXIT_AND_WAIT([ovn-northd]) +- +-as +-OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d +-/connection dropped.*/d"]) +-AT_CLEANUP +- + OVN_FOR_EACH_NORTHD([ + AT_SETUP([load balancing affinity sessions - IPv4]) + AT_KEYWORDS([ovnlb]) +@@ -365,7 +146,7 @@ tcp,orig=(src=172.16.1.2,dst=172.16.1.100,sport=,dport=),reply + ]) + + dp_key=$(printf "0x%x" $(fetch_column datapath tunnel_key external_ids:name=R2)) +-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=78 --no-stats | sed -e 's/load:0xc0a80[[0-9]]02/load:0xc0a8002/'], [0], [dnl ++AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=78 --no-stats | strip_cookie | sed -e 's/load:0xc0a80[[0-9]]02/load:0xc0a8002/'], [0], [dnl + table=78, idle_timeout=60, tcp,metadata=$dp_key,nw_src=172.16.1.2,nw_dst=172.16.1.100,tp_dst=8080 actions=load:0x1->NXM_NX_REG10[[14]],load:0xc0a8002->NXM_NX_REG4[[]],load:0x50->NXM_NX_REG8[[0..15]] + ]) + +@@ -588,31 +369,27 @@ ovn-nbctl lr-route-add R2 fd12::/64 fd20::1 + # Logical port 'foo1' in switch 'foo'. + ADD_NAMESPACES(foo1) + ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \ +- "fd11::1") +-OVS_WAIT_UNTIL([test "$(ip -n foo1 a | grep fd11::2 | grep tentative)" = ""]) ++ "fd11::1", "nodad") + ovn-nbctl lsp-add foo foo1 \ + -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2" + + # Logical port 'alice1' in switch 'alice'. + ADD_NAMESPACES(alice1) + ADD_VETH(alice1, alice1, br-int, "fd72::2/64", "f0:00:00:01:02:04", \ +- "fd72::1") +-OVS_WAIT_UNTIL([test "$(ip -n alice1 a | grep fd72::2 | grep tentative)" = ""]) ++ "fd72::1", "nodad") + ovn-nbctl lsp-add alice alice1 \ + -- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd72::2" + + # Logical port 'bar1' in switch 'bar'. + ADD_NAMESPACES(bar1) + ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:05", \ +-"fd12::1") +-OVS_WAIT_UNTIL([test "$(ip -n bar1 a | grep fd12::2 | grep tentative)" = ""]) ++ "fd12::1", "nodad") + ovn-nbctl lsp-add bar bar1 \ + -- lsp-set-addresses bar1 "f0:00:00:01:02:05 fd12::2" + + ADD_NAMESPACES(bar2) + ADD_VETH(bar2, bar2, br-int, "fd12::3/64", "e0:00:00:01:02:05", \ +-"fd12::1") +-OVS_WAIT_UNTIL([test "$(ip -n bar2 a | grep fd12::3 | grep tentative)" = ""]) ++ "fd12::1", "nodad") + ovn-nbctl lsp-add bar bar2 \ + -- lsp-set-addresses bar2 "e0:00:00:01:02:05 fd12::3" + +@@ -666,7 +443,7 @@ tcp,orig=(src=fd72::2,dst=fd30::1,sport=,dport=),reply=(src=fd + ]) + + dp_key=$(printf "0x%x" $(fetch_column datapath tunnel_key external_ids:name=R2)) +-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=78 --no-stats | sed -e 's/load:0xfd1[[0-9]]000000000000/load:0xfd1000000000000/'], [0], [dnl ++AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=78 --no-stats | strip_cookie | sed -e 's/load:0xfd1[[0-9]]000000000000/load:0xfd1000000000000/'], [0], [dnl + table=78, idle_timeout=60, tcp6,metadata=$dp_key,ipv6_src=fd72::2,ipv6_dst=fd30::1,tp_dst=8080 actions=load:0x1->NXM_NX_REG10[[14]],load:0x2->NXM_NX_XXREG1[[0..63]],load:0xfd1000000000000->NXM_NX_XXREG1[[64..127]],load:0x50->NXM_NX_REG8[[0..15]] + ]) + +@@ -1115,3 +892,155 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d + /connection dropped.*/d"]) + AT_CLEANUP + ]) ++ ++OVN_FOR_EACH_NORTHD([ ++AT_SETUP([LR with SNAT fragmentation needed for external server]) ++AT_KEYWORDS([ovnlb]) ++ ++CHECK_CONNTRACK() ++CHECK_CONNTRACK_NAT() ++ ++ovn_start ++OVS_TRAFFIC_VSWITCHD_START() ++ADD_BR([br-int]) ++ADD_BR([br-ext]) ++ ++dnl Logical network: ++dnl 2 logical switches "public" (192.168.1.0/24) and "internal" (172.16.1.0/24) ++dnl connected to a router lr. ++dnl internal has a client. ++dnl server is connected through localnet. ++dnl ++dnl Server IP 192.168.1.2 MTU 900 ++dnl Client IP 172.16.1.2 MTU 800 ++dnl ++dnl SNAT for internal 172.16.1.2/24 with router ip 192.168.1.1. ++ ++check ovs-ofctl add-flow br-ext action=normal ++# Set external-ids in br-int needed for ovn-controller ++check ovs-vsctl \ ++ -- set Open_vSwitch . external-ids:system-id=hv1 \ ++ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ ++ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ ++ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ ++ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true \ ++ -- set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext ++ ++dnl Start ovn-controller ++start_daemon ovn-controller ++ ++check ovn-nbctl lr-add lr ++check ovn-nbctl ls-add internal ++check ovn-nbctl ls-add public ++ ++check ovn-nbctl lrp-add lr lr-pub 00:00:01:01:02:03 192.168.1.1/24 ++check ovn-nbctl lsp-add public pub-lr -- set Logical_Switch_Port pub-lr \ ++ type=router options:router-port=lr-pub addresses=\"00:00:01:01:02:03\" ++ ++check ovn-nbctl lrp-add lr lr-internal 00:00:01:01:02:04 172.16.1.1/24 ++check ovn-nbctl lsp-add internal internal-lr -- set Logical_Switch_Port internal-lr \ ++ type=router options:router-port=lr-internal addresses=\"00:00:01:01:02:04\" ++ ++ovn-nbctl lsp-add public ln_port \ ++ -- lsp-set-addresses ln_port unknown \ ++ -- lsp-set-type ln_port localnet \ ++ -- lsp-set-options ln_port network_name=phynet ++ ++ADD_NAMESPACES(server) ++ADD_VETH([server], [server], [br-ext], ["192.168.1.2/24"], ++ ["f0:00:00:01:02:03"], ["192.168.1.1"]) ++NS_EXEC([server], [ip l set dev server mtu 900]) ++NS_EXEC([server], [ip l show dev server]) ++ ++ADD_NAMESPACES(client) ++ADD_VETH([client], [client], [br-int], ["172.16.1.2/24"], ++ ["f0:00:0f:01:02:03"], ["172.16.1.1"]) ++NS_EXEC([client], [ip l set dev client mtu 800]) ++NS_EXEC([client], [ip l show dev client]) ++check ovn-nbctl lsp-add internal client \ ++ -- lsp-set-addresses client "f0:00:0f:01:02:03 172.16.1.2" ++ ++dnl Config OVN load-balancer with a VIP. (not necessary, but if we do not ++dnl have a load balancer and comment out snat, we will receive a stray fragment ++dnl on the client side.) ++dnl check ovn-nbctl lb-add lb1 192.168.1.20:4242 172.16.1.2:4242 udp ++dnl check ovn-nbctl lr-lb-add lr lb1 ++check ovn-nbctl set logical_router lr options:chassis=hv1 ++check ovn-nbctl set logical_router_port lr-internal options:gateway_mtu=800 ++ ++check ovn-nbctl lr-nat-add lr snat 192.168.1.1 172.16.1.2/24 ++ ++check ovn-nbctl --wait=hv sync ++ ++ovn-nbctl show ++ovs-vsctl show ++ovn-appctl -t ovn-controller vlog/set vconn:file:dbg pinctrl:file:dbg ++ ++AT_DATA([server.py], [dnl ++import socket ++ ++sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ++ ++server_address = '192.168.1.2' ++server_port = 4242 ++ ++server = (server_address, server_port) ++sock.bind(server) ++print("Listening on ", server_address, ":", str(server_port), flush=True) ++ ++while True: ++ payload, client_address = sock.recvfrom(1000) ++ print("Received data from ", str(client_address), ": ", payload) ++ sent = sock.sendto(b"x" * 1017, client_address) ++ print("Sent back: ", str(sent), "bytes", flush=True) ++]) ++NETNS_DAEMONIZE([server], [$PYTHON3 ./server.py > server.log], [server.pid]) ++ ++dnl Collect packets on server side. ++NETNS_DAEMONIZE([server], [tcpdump -l -U -i server -vnne \ ++ 'ip and (icmp or udp)' > server.tcpdump 2>server_err], [tcpdump0.pid]) ++OVS_WAIT_UNTIL([grep "listening" server_err]) ++ ++dnl Collect packets on client side. ++NETNS_DAEMONIZE([client], [tcpdump -l -U -i client -vnne \ ++ 'ip and (icmp or udp)' > client.tcpdump 2>client_err], [tcpdump1.pid]) ++OVS_WAIT_UNTIL([grep "listening" client_err]) ++ ++dnl Send two packets to the server with a short interval. ++dnl First packet should generate 'needs frag', the second should result in ++dnl corectly fragmented reply. ++AT_DATA([client.py], [dnl ++import socket ++import time ++ ++sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ++sock.sendto(b"x" * 7, ("192.168.1.2", 4242)) ++time.sleep(1) ++sock.sendto(b"x" * 7, ("192.168.1.2", 4242)) ++time.sleep(5) ++]) ++NS_CHECK_EXEC([client], [$PYTHON3 ./client.py]) ++ ++dnl Expecting 2 outgoing packets and 2 fragments back - 8 lines total. ++OVS_WAIT_UNTIL([test "$(cat client.tcpdump | wc -l)" = "8"]) ++ ++ovn-appctl -t ovn-controller vlog/set info ++ ++OVS_APP_EXIT_AND_WAIT([ovn-controller]) ++ ++as ovn-sb ++OVS_APP_EXIT_AND_WAIT([ovsdb-server]) ++ ++as ovn-nb ++OVS_APP_EXIT_AND_WAIT([ovsdb-server]) ++ ++as northd ++OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) ++ ++as ++OVS_TRAFFIC_VSWITCHD_STOP([" ++ /failed to query port patch-.*/d ++ /connection dropped.*/d ++"]) ++AT_CLEANUP ++]) +diff --git a/tests/system-ovn.at b/tests/system-ovn.at +index 59d0cb2a0..3ecf1db42 100644 +--- a/tests/system-ovn.at ++++ b/tests/system-ovn.at +@@ -251,24 +251,21 @@ ovn-nbctl lr-route-add R2 fd12::/64 fd00::1 + # Logical port 'foo1' in switch 'foo'. + ADD_NAMESPACES(foo1) + ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \ +- "fd11::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd11::2 | grep tentative)" = ""]) ++ "fd11::1", "nodad") + ovn-nbctl lsp-add foo foo1 \ + -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2" + + # Logical port 'alice1' in switch 'alice'. + ADD_NAMESPACES(alice1) + ADD_VETH(alice1, alice1, br-int, "fd21::2/64", "f0:00:00:01:02:04", \ +- "fd21::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd21::2 | grep tentative)" = ""]) ++ "fd21::1", "nodad") + ovn-nbctl lsp-add alice alice1 \ + -- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd21::2" + + # Logical port 'bar1' in switch 'bar'. + ADD_NAMESPACES(bar1) + ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:05", \ +- "fd12::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec bar1 ip a | grep fd12::2 | grep tentative)" = ""]) ++ "fd12::1", "nodad") + ovn-nbctl lsp-add bar bar1 \ + -- lsp-set-addresses bar1 "f0:00:00:01:02:05 fd12::2" + +@@ -538,16 +535,14 @@ ovn-nbctl lr-route-add R2 fd10::/64 fd20::1 + # Logical port 'foo1' in switch 'foo'. + ADD_NAMESPACES(foo1) + ADD_VETH(foo1, foo1, br-int, "fd10::2/64", "f0:00:00:01:02:03", \ +- "fd10::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd10::2 | grep tentative)" = ""]) ++ "fd10::1", "nodad") + ovn-nbctl lsp-add foo foo1 \ + -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd10::2" + + # Logical port 'alice1' in switch 'alice'. + ADD_NAMESPACES(alice1) + ADD_VETH(alice1, alice1, br-int, "fd30::2/64", "f0:00:00:01:02:04", \ +- "fd30::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd30::2 | grep tentative)" = ""]) ++ "fd30::1", "nodad") + ovn-nbctl lsp-add alice alice1 \ + -- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd30::2" + +@@ -908,32 +903,28 @@ ovn-nbctl set logical_router R3 options:dnat_force_snat_ip=fd20::3 + # Logical port 'foo1' in switch 'foo'. + ADD_NAMESPACES(foo1) + ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \ +- "fd11::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd11::2 | grep tentative)" = ""]) ++ "fd11::1", "nodad") + ovn-nbctl lsp-add foo foo1 \ + -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2" + + # Logical port 'alice1' in switch 'alice'. + ADD_NAMESPACES(alice1) + ADD_VETH(alice1, alice1, br-int, "fd30::3/64", "f0:00:00:01:02:04", \ +- "fd30::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd30::3 | grep tentative)" = ""]) ++ "fd30::1", "nodad") + ovn-nbctl lsp-add alice alice1 \ + -- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd30::3" + + # Logical port 'bar1' in switch 'bar'. + ADD_NAMESPACES(bar1) + ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:05", \ +- "fd12::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec bar1 ip a | grep fd12::2 | grep tentative)" = ""]) ++ "fd12::1", "nodad") + ovn-nbctl lsp-add bar bar1 \ + -- lsp-set-addresses bar1 "f0:00:00:01:02:05 fd12::2" + + # Logical port 'bob1' in switch 'bob'. + ADD_NAMESPACES(bob1) + ADD_VETH(bob1, bob1, br-int, "fd30::4/64", "f0:00:00:01:02:06", \ +- "fd30::2") +-OVS_WAIT_UNTIL([test "$(ip netns exec bob1 ip a | grep fd30::4 | grep tentative)" = ""]) ++ "fd30::2", "nodad") + ovn-nbctl lsp-add bob bob1 \ + -- lsp-set-addresses bob1 "f0:00:00:01:02:06 fd30::4" + +@@ -1149,8 +1140,7 @@ ovn-nbctl lsp-add foo foo1 \ + + ADD_NAMESPACES(foo16) + ADD_VETH(foo16, foo16, br-int, "fd11::2/64", "f0:00:00:02:02:03", \ +- "fd11::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec foo16 ip a | grep fd11::2 | grep tentative)" = ""]) ++ "fd11::1", "nodad") + ovn-nbctl lsp-add foo foo16 \ + -- lsp-set-addresses foo16 "f0:00:00:02:02:03 fd11::2" + +@@ -1163,8 +1153,7 @@ ovn-nbctl lsp-add alice alice1 \ + + ADD_NAMESPACES(alice16) + ADD_VETH(alice16, alice16, br-int, "fd30::3/64", "f0:00:00:02:02:04", \ +- "fd30::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec alice16 ip a | grep fd30::3 | grep tentative)" = ""]) ++ "fd30::1", "nodad") + ovn-nbctl lsp-add alice alice16 \ + -- lsp-set-addresses alice16 "f0:00:00:02:02:04 fd30::3" + +@@ -1177,8 +1166,7 @@ ovn-nbctl lsp-add bar bar1 \ + + ADD_NAMESPACES(bar16) + ADD_VETH(bar16, bar16, br-int, "fd12::2/64", "f0:00:00:02:02:05", \ +- "fd12::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec bar16 ip a | grep fd12::2 | grep tentative)" = ""]) ++ "fd12::1", "nodad") + ovn-nbctl lsp-add bar bar16 \ + -- lsp-set-addresses bar16 "f0:00:00:02:02:05 fd12::2" + +@@ -1191,8 +1179,7 @@ ovn-nbctl lsp-add bob bob1 \ + + ADD_NAMESPACES(bob16) + ADD_VETH(bob16, bob16, br-int, "fd30::4/64", "f0:00:00:02:02:06", \ +- "fd30::2") +-OVS_WAIT_UNTIL([test "$(ip netns exec bob16 ip a | grep fd30::4 | grep tentative)" = ""]) ++ "fd30::2", "nodad") + ovn-nbctl lsp-add bob bob16 \ + -- lsp-set-addresses bob16 "f0:00:00:02:02:06 fd30::4" + +@@ -2376,14 +2363,10 @@ ADD_NAMESPACES(server) + ADD_VETH(s1, server, br-ext, "172.16.1.100/24", "1a:00:00:00:00:01", \ + "172.16.1.1") + +-OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep fe80 | grep tentative)" = ""]) +- + ADD_NAMESPACES(client) + ADD_VETH(c1, client, br-ext, "172.16.1.110/24", "1a:00:00:00:00:02", \ + "172.16.1.1") + +-OVS_WAIT_UNTIL([test "$(ip netns exec client ip a | grep fe80 | grep tentative)" = ""]) +- + # Start webservers in 'server'. + OVS_START_L7([server], [http]) + +@@ -3670,32 +3653,28 @@ ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ + # Logical port 'foo1' in switch 'foo'. + ADD_NAMESPACES(foo1) + ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \ +- "fd11::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd11::2 | grep tentative)" = ""]) ++ "fd11::1", "nodad") + ovn-nbctl lsp-add foo foo1 \ + -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2" + + # Logical port 'foo2' in switch 'foo'. + ADD_NAMESPACES(foo2) + ADD_VETH(foo2, foo2, br-int, "fd11::3/64", "f0:00:00:01:02:06", \ +- "fd11::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec foo2 ip a | grep fd11::3 | grep tentative)" = ""]) ++ "fd11::1", "nodad") + ovn-nbctl lsp-add foo foo2 \ + -- lsp-set-addresses foo2 "f0:00:00:01:02:06 fd11::3" + + # Logical port 'bar1' in switch 'bar'. + ADD_NAMESPACES(bar1) + ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:04", \ +- "fd12::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec bar1 ip a | grep fd12::2 | grep tentative)" = ""]) ++ "fd12::1", "nodad") + ovn-nbctl lsp-add bar bar1 \ + -- lsp-set-addresses bar1 "f0:00:00:01:02:04 fd12::2" + + # Logical port 'alice1' in switch 'alice'. + ADD_NAMESPACES(alice1) + ADD_VETH(alice1, alice1, br-int, "fd20::2/64", "f0:00:00:01:02:05", \ +- "fd20::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd20::2 | grep tentative)" = ""]) ++ "fd20::1", "nodad") + ovn-nbctl lsp-add alice alice1 \ + -- lsp-set-addresses alice1 "f0:00:00:01:02:05 fd20::2" + +@@ -4018,32 +3997,28 @@ ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ + # Logical port 'foo1' in switch 'foo'. + ADD_NAMESPACES(foo1) + ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \ +- "fd11::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd11::2 | grep tentative)" = ""]) ++ "fd11::1", "nodad") + ovn-nbctl lsp-add foo foo1 \ + -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2" + + # Logical port 'foo2' in switch 'foo'. + ADD_NAMESPACES(foo2) + ADD_VETH(foo2, foo2, br-int, "fd11::3/64", "f0:00:00:01:02:06", \ +- "fd11::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec foo2 ip a | grep fd11::3 | grep tentative)" = ""]) ++ "fd11::1", "nodad") + ovn-nbctl lsp-add foo foo2 \ + -- lsp-set-addresses foo2 "f0:00:00:01:02:06 fd11::3" + + # Logical port 'bar1' in switch 'bar'. + ADD_NAMESPACES(bar1) + ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:04", \ +- "fd12::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec bar1 ip a | grep fd12::2 | grep tentative)" = ""]) ++ "fd12::1", "nodad") + ovn-nbctl lsp-add bar bar1 \ + -- lsp-set-addresses bar1 "f0:00:00:01:02:04 fd12::2" + + # Logical port 'alice1' in switch 'alice'. + ADD_NAMESPACES(alice1) + ADD_VETH(alice1, alice1, br-int, "fd20::2/64", "f0:00:00:01:02:05", \ +- "fd20::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd20::2 | grep tentative)" = ""]) ++ "fd20::1", "nodad") + ovn-nbctl lsp-add alice alice1 \ + -- lsp-set-addresses alice1 "f0:00:00:01:02:05 fd20::2" + +@@ -4928,8 +4903,7 @@ ovn-nbctl lsp-add sw sw-rtr \ + -- lsp-set-options sw-rtr router-port=rtr-sw + + ADD_NAMESPACES(lsp) +-ADD_VETH(lsp, lsp, br-int, "4200::1/64", "00:00:00:00:00:01", "4200::00ff") +-OVS_WAIT_UNTIL([test "$(ip netns exec lsp ip a | grep 4200::1 | grep tentative)" = ""]) ++ADD_VETH(lsp, lsp, br-int, "4200::1/64", "00:00:00:00:00:01", "4200::00ff", "nodad") + ovn-nbctl --wait=hv -t 3 sync + + # Start IPv6 TCP server on lsp. +@@ -5058,8 +5032,8 @@ ADD_NAMESPACES(sw0-p2-rej) + ADD_VETH(sw0-p2-rej, sw0-p2-rej, br-int, "10.0.0.4/24", "50:54:00:00:00:04", \ + "10.0.0.1") + +-NS_CHECK_EXEC([sw0-p1-rej], [ip a a aef0::3/64 dev sw0-p1-rej], [0]) +-NS_CHECK_EXEC([sw0-p2-rej], [ip a a aef0::4/64 dev sw0-p2-rej], [0]) ++NS_CHECK_EXEC([sw0-p1-rej], [ip a a aef0::3/64 dev sw0-p1-rej nodad], [0]) ++NS_CHECK_EXEC([sw0-p2-rej], [ip a a aef0::4/64 dev sw0-p2-rej nodad], [0]) + + ADD_NAMESPACES(sw1-p1-rej) + ADD_VETH(sw1-p1-rej, sw1-p1-rej, br-int, "20.0.0.3/24", "40:54:00:00:00:03", \ +@@ -5099,9 +5073,6 @@ NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej tcp port 80 > sw0-p2-r + #Wait for tcpdump to get started before generating first packets + OVS_WAIT_UNTIL([test 1 = $(cat err | grep -c listening)]) + +-OVS_WAIT_UNTIL([test "$(ip netns exec sw0-p1-rej ip a | grep aef0::3 | grep tentative)" = ""]) +-OVS_WAIT_UNTIL([test "$(ip netns exec sw0-p2-rej ip a | grep aef0::4 | grep tentative)" = ""]) +- + OVS_WAIT_UNTIL([ + ip netns exec sw0-p2-rej nc -vz6 aef0::3 80 2>&1 | grep -i 'connection refused' + ]) +@@ -5308,8 +5279,8 @@ ADD_NAMESPACES(sw0-p2-rej) + ADD_VETH(sw0-p2-rej, sw0-p2-rej, br-int, "10.0.0.4/24", "50:54:00:00:00:04", \ + "10.0.0.1") + +-NS_CHECK_EXEC([sw0-p1-rej], [ip a a aef0::3/64 dev sw0-p1-rej], [0]) +-NS_CHECK_EXEC([sw0-p2-rej], [ip a a aef0::4/64 dev sw0-p2-rej], [0]) ++NS_CHECK_EXEC([sw0-p1-rej], [ip a a aef0::3/64 dev sw0-p1-rej nodad], [0]) ++NS_CHECK_EXEC([sw0-p2-rej], [ip a a aef0::4/64 dev sw0-p2-rej nodad], [0]) + + ADD_NAMESPACES(sw1-p1-rej) + ADD_VETH(sw1-p1-rej, sw1-p1-rej, br-int, "20.0.0.3/24", "40:54:00:00:00:03", \ +@@ -5349,9 +5320,6 @@ NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej tcp port 80 > sw0-p2-r + #Wait for tcpdump to get started before generating first packets + OVS_WAIT_UNTIL([test 1 = $(cat err | grep -c listening)]) + +-OVS_WAIT_UNTIL([test "$(ip netns exec sw0-p1-rej ip a | grep aef0::3 | grep tentative)" = ""]) +-OVS_WAIT_UNTIL([test "$(ip netns exec sw0-p2-rej ip a | grep aef0::4 | grep tentative)" = ""]) +- + OVS_WAIT_UNTIL([ + ip netns exec sw0-p2-rej nc -vz6 aef0::3 80 2>&1 | grep -i 'connection refused' + ]) +@@ -5878,12 +5846,10 @@ check ovn-nbctl \ + -- ls-lb-add ls lb-test + + ADD_NAMESPACES(vm1) +-ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec vm1 ip a | grep 4242::2 | grep tentative)" = ""]) ++ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1", "nodad") + + ADD_NAMESPACES(vm2) +-ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec vm2 ip a | grep 4242::3 | grep tentative)" = ""]) ++ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1", "nodad") + + # Wait for ovn-controller to catch up. + wait_for_ports_up +@@ -6306,8 +6272,7 @@ NS_CHECK_EXEC([alice1], [sysctl -w net.ipv6.conf.default.router_solicitations=1] + net.ipv6.conf.default.router_solicitations = 1 + ]) + ADD_VETH(alice1, alice1, br-int, "fd01::2/64", "f0:00:00:01:02:04", \ +- "fd01::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd01::2 | grep tentative)" = ""]) ++ "fd01::1", "nodad") + ovn-nbctl lsp-add alice alice1 \ + -- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd01::2" + # Add neighbour MAC address to avoid sending IPv6 NS messages which could +@@ -6322,8 +6287,7 @@ NS_CHECK_EXEC([bob1], [sysctl -w net.ipv6.conf.default.router_solicitations=1], + net.ipv6.conf.default.router_solicitations = 1 + ]) + ADD_VETH(bob1, bob1, br-int, "fd07::1/64", "f0:00:00:01:02:06", \ +- "fd07::2") +-OVS_WAIT_UNTIL([test "$(ip netns exec bob1 ip a | grep fd07::1 | grep tentative)" = ""]) ++ "fd07::2", "nodad") + # Add neighbour MAC addresses to avoid sending IPv6 NS messages which could + # cause datapath flows to be evicted + NS_CHECK_EXEC([bob1], [ip -6 neigh add fd07::2 lladdr 00:00:02:01:02:03 dev bob1], [0]) +@@ -6513,8 +6477,6 @@ ADD_NAMESPACES(ext-foo) + ADD_VETH(ext-foo, ext-foo, br-ext, "172.16.1.100/24", "00:10:10:01:02:13", \ + "172.16.1.1") + +-OVS_WAIT_UNTIL([test "$(ip netns exec ext-foo ip a | grep fe80 | grep tentative)" = ""]) +- + AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext]) + ovn-nbctl lsp-add public public1 \ + -- lsp-set-addresses public1 unknown \ +@@ -6630,6 +6592,28 @@ AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_min_rate=400000]) + AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_max_rate=600000]) + AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_burst=6000000]) + ++OVS_WAIT_UNTIL([tc qdisc show | grep -q 'htb 1: dev ovs-public']) ++OVS_WAIT_UNTIL([tc class show dev ovs-public | \ ++ grep -q 'class htb .* rate 200Kbit ceil 300Kbit burst 375000b cburst 375000b']) ++ ++OVS_WAIT_UNTIL([tc qdisc show | grep -q 'htb 1: dev ovs-ext']) ++OVS_WAIT_UNTIL([tc class show dev ovs-ext | \ ++ grep -q 'class htb .* rate 400Kbit ceil 600Kbit burst 750000b cburst 750000b']) ++ ++# The same now with ovs db read only ++# ++AT_CHECK([ovn-nbctl remove Logical_Switch_Port ext options qos_min_rate=400000]) ++AT_CHECK([ovn-nbctl remove Logical_Switch_Port ext options qos_max_rate=600000]) ++AT_CHECK([ovn-nbctl remove Logical_Switch_Port ext options qos_burst=6000000]) ++OVS_WAIT_UNTIL([test "$(tc class show dev ovs-ext | grep 'class htb')" == ""]) ++ ++sleep_ovsdb . ++ ++AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_min_rate=400000]) ++AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_max_rate=600000]) ++AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_burst=6000000]) ++wake_up_ovsdb . ++ + OVS_WAIT_UNTIL([tc qdisc show | grep -q 'htb 1: dev ovs-public']) + OVS_WAIT_UNTIL([tc class show dev ovs-public | \ + grep -q 'class htb .* rate 200Kbit ceil 300Kbit burst 375000b cburst 375000b']) +@@ -7348,9 +7332,9 @@ check ovn-nbctl lsp-add public public1 \ + + NS_EXEC([sw01], [tcpdump -l -n -i sw01 icmp -Q in > reject.pcap &]) + check ovn-nbctl meter-add acl-meter drop 1 pktps 0 +-check ovn-nbctl --wait=hv copp-add copp0 reject acl-meter +-check ovn-nbctl --wait=hv ls-copp-add copp0 sw0 +-check ovn-nbctl acl-add sw0 from-lport 1002 'inport == "sw01" && ip && udp' reject ++check ovn-nbctl copp-add copp0 reject acl-meter ++check ovn-nbctl ls-copp-add copp0 sw0 ++check ovn-nbctl --wait=hv acl-add sw0 from-lport 1002 'inport == "sw01" && ip && udp' reject + + AT_CHECK([ovn-nbctl copp-list copp0], [0], [dnl + reject: acl-meter +@@ -7371,7 +7355,7 @@ rm -f reject.pcap + + # Let's update the meter + NS_EXEC([sw01], [tcpdump -l -n -i sw01 icmp -Q in > reject.pcap &]) +-check ovn-nbctl --may-exist meter-add acl-meter drop 10 pktps 0 ++check ovn-nbctl --may-exist --wait=hv meter-add acl-meter drop 10 pktps 0 + ip netns exec sw01 scapy -H <<-EOF + p = IP(src="192.168.1.2", dst="192.168.1.1") / UDP(dport = 12345) / Raw(b"X"*64) + send (p, iface='sw01', loop = 0, verbose = 0, count = 40) +@@ -7402,7 +7386,7 @@ kill $(pidof tcpdump) + + NS_EXEC([server], [tcpdump -l -n -i s1 arp[[24:4]]=0xac100164 > arp.pcap &]) + check ovn-nbctl meter-add arp-meter drop 1 pktps 0 +-check ovn-nbctl --wait=hv copp-add copp1 arp-resolve arp-meter ++check ovn-nbctl copp-add copp1 arp-resolve arp-meter + check ovn-nbctl --wait=hv lr-copp-add copp1 R1 + AT_CHECK([ovn-nbctl copp-list copp1], [0], [dnl + arp-resolve: arp-meter +@@ -7421,7 +7405,7 @@ OVS_WAIT_UNTIL([ + kill $(pidof tcpdump) + + check ovn-nbctl meter-add icmp-meter drop 1 pktps 0 +-check ovn-nbctl --wait=hv copp-add copp2 icmp4-error icmp-meter ++check ovn-nbctl copp-add copp2 icmp4-error icmp-meter + check ovn-nbctl --wait=hv lr-copp-add copp2 R1 + AT_CHECK([ovn-nbctl copp-list copp2 |grep icmp4-error], [0], [dnl + icmp4-error: icmp-meter +@@ -7441,7 +7425,7 @@ OVS_WAIT_UNTIL([ + kill $(pidof tcpdump) + + check ovn-nbctl meter-add bfd-meter drop 1 pktps 0 +-check ovn-nbctl --wait=hv copp-add copp3 bfd bfd-meter ++check ovn-nbctl copp-add copp3 bfd bfd-meter + check ovn-nbctl --wait=hv lr-copp-add copp3 R1 + AT_CHECK([ovn-nbctl copp-list copp3 |grep bfd], [0], [dnl + bfd: bfd-meter +@@ -7471,7 +7455,7 @@ kill $(pidof tcpdump) + + check ovn-nbctl set nb_global . options:svc_monitor_mac="33:33:33:33:33:33" + check ovn-nbctl meter-add svc-meter drop 1 pktps 0 +-check ovn-nbctl --wait=hv copp-add copp4 svc-monitor svc-meter ++check ovn-nbctl copp-add copp4 svc-monitor svc-meter + check ovn-nbctl --wait=hv ls-copp-add copp4 sw0 + check ovn-appctl -t ovn-controller vlog/set vconn:dbg + AT_CHECK([ovn-nbctl copp-list copp4], [0], [dnl +@@ -8696,22 +8680,19 @@ check ovn-nbctl lr-nat-add lr1 snat 172.16.1.10 192.168.1.0/24 + check ovn-nbctl lr-nat-add lr1 snat 1711::10 2001::/64 + + ADD_NAMESPACES(ls1p1) +-ADD_VETH(ls1p1, ls1p1, br-int, "192.168.1.1/24", "00:00:00:01:01:01", \ +- "192.168.1.254", , "2001::1/64", "2001::a") ++ADD_VETH(ls1p1, ls1p1, br-int, "2001::1/64", "00:00:00:01:01:01", \ ++ "2001::a", "nodad", "192.168.1.1/24", "192.168.1.254") + + ADD_NAMESPACES(ls1p2) +-ADD_VETH(ls1p2, ls1p2, br-int, "192.168.1.2/24", "00:00:00:01:01:02", \ +- "192.168.1.254", , "2001::2/64", "2001::a") ++ADD_VETH(ls1p2, ls1p2, br-int, "2001::2/64", "00:00:00:01:01:02", \ ++ "2001::a", "nodad", "192.168.1.2/24", "192.168.1.254") + + ADD_NAMESPACES(ext1) +-ADD_VETH(ext1, ext1, br0, "172.16.1.1/24", "00:ee:00:01:01:01", \ +- "172.16.1.254", , "1711::1/64", "1711::a") ++ADD_VETH(ext1, ext1, br0, "1711::1/64", "00:ee:00:01:01:01", \ ++ "1711::a", "nodad", "172.16.1.1/24", "172.16.1.254") + + check ovn-nbctl --wait=hv sync + wait_for_ports_up +-OVS_WAIT_UNTIL([test "$(ip netns exec ls1p1 ip a | grep 2001::1 | grep tentative)" = ""]) +-OVS_WAIT_UNTIL([test "$(ip netns exec ls1p2 ip a | grep 2002::2 | grep tentative)" = ""]) +-OVS_WAIT_UNTIL([test "$(ip netns exec ext1 ip a | grep 1711::1 | grep tentative)" = ""]) + + NS_CHECK_EXEC([ls1p1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.1 | FORMAT_PING], \ + [0], [dnl +@@ -9231,8 +9212,9 @@ name: 'vport4' value: '999' + NETNS_DAEMONIZE([vm1], [nc -k -l 42.42.42.2 4242], [nc-vm1.pid]) + + NETNS_DAEMONIZE([vm1], +- [tcpdump -n -i vm1 -nnleX -c6 udp and dst 42.42.42.2 and dst port 4343 > vm1.pcap 2>/dev/null], ++ [tcpdump -n -i vm1 -nnleX -c6 udp and dst 42.42.42.2 and dst port 4343 > vm1.pcap 2> vm1.pcap.stderr], + [tcpdump1.pid]) ++OVS_WAIT_UNTIL([grep "listening" vm1.pcap.stderr]) + + # Make sure connecting to the VIP works (hairpin, via ls and via lr). + NS_CHECK_EXEC([vm1], [nc 66.66.66.66 666 -z], [0], [ignore], [ignore]) +@@ -9355,16 +9337,13 @@ check ovn-nbctl --template lb-add lb-test-udp2 "^vip:^vport4" "[[4242::2]]:4343" + -- lr-lb-add rtr lb-test-udp2 + + ADD_NAMESPACES(vm1) +-ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec vm1 ip a | grep 4242::2 | grep tentative)" = ""]) ++ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1", "nodad") + + ADD_NAMESPACES(vm2) +-ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec vm2 ip a | grep 4242::3 | grep tentative)" = ""]) ++ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1", "nodad") + + ADD_NAMESPACES(vm3) +-ADD_VETH(vm3, vm3, br-int, "4343::2/64", "00:00:00:00:00:03", "4343::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec vm3 ip a | grep 4343::2 | grep tentative)" = ""]) ++ADD_VETH(vm3, vm3, br-int, "4343::2/64", "00:00:00:00:00:03", "4343::1", "nodad") + + # Wait for ovn-controller to catch up. + wait_for_ports_up +@@ -9385,8 +9364,9 @@ name: 'vport4' value: '999' + NETNS_DAEMONIZE([vm1], [nc -k -l 4242::2 4242], [nc-vm1.pid]) + + NETNS_DAEMONIZE([vm1], +- [tcpdump -n -i vm1 -nnleX -c6 udp and dst 4242::2 and dst port 4343 > vm1.pcap 2>/dev/null], ++ [tcpdump -n -i vm1 -nnleX -c6 udp and dst 4242::2 and dst port 4343 > vm1.pcap 2> vm1.pcap.stderr], + [tcpdump1.pid]) ++OVS_WAIT_UNTIL([grep "listening" vm1.pcap.stderr]) + + # Make sure connecting to the VIP works (hairpin, via ls and via lr). + NS_CHECK_EXEC([vm1], [nc 6666::1 666 -z], [0], [ignore], [ignore]) +@@ -10825,32 +10805,28 @@ check ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \ + # Logical port 'foo1' in switch 'foo'. + ADD_NAMESPACES(foo1) + ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \ +- "fd7b:6b4d:7b25:d22f::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd11::2 | grep tentative)" = ""]) ++ "fd7b:6b4d:7b25:d22f::1", "nodad") + check ovn-nbctl lsp-add foo foo1 \ + -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2" + + # Logical port 'foo2' in switch 'foo'. + ADD_NAMESPACES(foo2) + ADD_VETH(foo2, foo2, br-int, "fd11::3/64", "f0:00:00:01:02:04", \ +- "fd7b:6b4d:7b25:d22f::2") +-OVS_WAIT_UNTIL([test "$(ip netns exec foo2 ip a | grep fd11::3 | grep tentative)" = ""]) ++ "fd7b:6b4d:7b25:d22f::2", "nodad") + check ovn-nbctl lsp-add foo foo2 \ + -- lsp-set-addresses foo2 "f0:00:00:01:02:04 fd11::3" + + # Logical port 'foo3' in switch 'foo'. + ADD_NAMESPACES(foo3) + ADD_VETH(foo3, foo3, br-int, "fd11::4/64", "f0:00:00:01:02:05", \ +- "fd7b:6b4d:7b25:d22d::1") +-OVS_WAIT_UNTIL([test "$(ip netns exec foo3 ip a | grep fd11::4 | grep tentative)" = ""]) ++ "fd7b:6b4d:7b25:d22d::1", "nodad") + check ovn-nbctl lsp-add foo foo3 \ + -- lsp-set-addresses foo3 "f0:00:00:01:02:05 fd11::4" + + # Logical port 'bar1' in switch 'bar'. + ADD_NAMESPACES(bar1) + ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:06", \ +-"fd7b:6b4d:7b25:d22f::3") +-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd12::2 | grep tentative)" = ""]) ++ "fd7b:6b4d:7b25:d22f::3", "nodad") + check ovn-nbctl lsp-add bar bar1 \ + -- lsp-set-addresses bar1 "f0:00:00:01:02:06 fd12::2" + +@@ -11686,7 +11662,7 @@ check ovn-nbctl lsp-add ls0 ls0-lr0 \ + -- lsp-set-options ls0-lr0 router-port=lr0-ls0 + + ADD_NAMESPACES(vif0) +-ADD_VETH(vif0, vif0, br-int, "fd00::2/64", "00:00:00:00:00:02", "fd00::1") ++ADD_VETH(vif0, vif0, br-int, "fd00::2/64", "00:00:00:00:00:02", "fd00::1", "nodad") + OVS_WAIT_UNTIL([test "$(ip netns exec vif0 ip a | grep fe80:: | grep tentative)" = ""]) + + check ovn-nbctl set logical_router lr0 options:always_learn_from_arp_request=false +@@ -11729,3 +11705,417 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d + + AT_CLEANUP + ]) ++ ++OVN_FOR_EACH_NORTHD([ ++AT_SETUP([ct_flush on logical router load balancer]) ++AT_KEYWORDS([ct-lr-flush]) ++CHECK_CONNTRACK() ++CHECK_CONNTRACK_NAT() ++ovn_start ++OVS_TRAFFIC_VSWITCHD_START() ++ADD_BR([br-int]) ++ ++# Set external-ids in br-int needed for ovn-controller ++ovs-vsctl \ ++ -- set Open_vSwitch . external-ids:system-id=hv1 \ ++ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ ++ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ ++ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ ++ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true ++ ++start_daemon ovn-controller ++ ++check ovn-nbctl lr-add R1 ++ ++check ovn-nbctl ls-add sw0 ++check ovn-nbctl ls-add public ++ ++check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24 ++check ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 ++ ++check ovn-nbctl set logical_router R1 options:chassis=hv1 ++ ++check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \ ++ type=router options:router-port=rp-sw0 \ ++ -- lsp-set-addresses sw0-rp router ++ ++check ovn-nbctl lsp-add sw0 sw0-vm \ ++ -- lsp-set-addresses sw0-vm "00:00:01:01:02:04 192.168.1.2/24" ++ ++check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \ ++ type=router options:router-port=rp-public \ ++ -- lsp-set-addresses public-rp router ++ ++check ovn-nbctl lsp-add public public-vm \ ++ -- lsp-set-addresses public-vm "00:00:02:01:02:04 172.16.1.2/24" ++ ++ADD_NAMESPACES(sw0-vm) ++ADD_VETH(sw0-vm, sw0-vm, br-int, "192.168.1.2/24", "00:00:01:01:02:04", \ ++ "192.168.1.1") ++ ++ADD_NAMESPACES(public-vm) ++ADD_VETH(public-vm, public-vm, br-int, "172.16.1.2/24", "00:00:02:01:02:04", \ ++ "172.16.1.1") ++ ++# Start webservers in 'server'. ++OVS_START_L7([sw0-vm], [http]) ++ ++# Create a load balancer and associate to R1 ++check ovn-nbctl lb-add lb1 172.16.1.150:80 192.168.1.2:80 \ ++ -- set load_balancer lb1 options:ct_flush="true" ++check ovn-nbctl lr-lb-add R1 lb1 ++ ++check ovn-nbctl --wait=hv sync ++ ++for i in $(seq 1 5); do ++ echo Request $i ++ NS_CHECK_EXEC([public-vm], [wget 172.16.1.150 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) ++done ++ ++OVS_WAIT_FOR_OUTPUT([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.150) | wc -l ], [0], [dnl ++1 ++]) ++ ++check ovn-nbctl --wait=hv lb-del lb1 ++ ++OVS_WAIT_FOR_OUTPUT([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.150) | wc -l ], [0], [dnl ++0 ++]) ++ ++check ovn-nbctl lb-add lb2 172.16.1.151:80 192.168.1.2:80 ++check ovn-nbctl lr-lb-add R1 lb2 ++ ++check ovn-nbctl --wait=hv sync ++ ++for i in $(seq 1 5); do ++ echo Request $i ++ NS_CHECK_EXEC([public-vm], [wget 172.16.1.151 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) ++done ++ ++OVS_WAIT_FOR_OUTPUT([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.151) | wc -l ], [0], [dnl ++1 ++]) ++ ++check ovn-nbctl --wait=hv lb-del lb2 ++ ++OVS_WAIT_FOR_OUTPUT([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.151) | wc -l ], [0], [dnl ++1 ++]) ++ ++OVS_APP_EXIT_AND_WAIT([ovn-controller]) ++ ++as ovn-sb ++OVS_APP_EXIT_AND_WAIT([ovsdb-server]) ++ ++as ovn-nb ++OVS_APP_EXIT_AND_WAIT([ovsdb-server]) ++ ++as northd ++OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) ++ ++as ++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d ++/Failed to acquire.*/d ++/connection dropped.*/d"]) ++AT_CLEANUP ++]) ++ ++AT_SETUP([load balancing in gateway router - SCTP]) ++AT_SKIP_IF([test $HAVE_SCTP = no]) ++AT_SKIP_IF([test $HAVE_NC = no]) ++AT_KEYWORDS([ovnlb sctp]) ++ ++# Make sure the SCTP kernel module is loaded. ++LOAD_MODULE([sctp]) ++ ++CHECK_CONNTRACK() ++CHECK_CONNTRACK_NAT() ++ovn_start ++OVS_TRAFFIC_VSWITCHD_START() ++ADD_BR([br-int]) ++ ++# Set external-ids in br-int needed for ovn-controller. ++ovs-vsctl \ ++ -- set Open_vSwitch . external-ids:system-id=hv1 \ ++ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ ++ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ ++ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ ++ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true ++ ++# Start the ovn-controller. ++start_daemon ovn-controller ++ ++# Logical network: ++# Two LRs - R1 and R2 that are connected to each other via LS "join" ++# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and ++# bar (192.168.2.0/24) connected to it. R2 has alice (172.16.1.0/24) connected ++# to it. R2 is a gateway router on which we add load-balancing rules. ++# ++# foo -- R1 -- join - R2 -- alice ++# | ++# bar ---- ++ ++ovn-nbctl create Logical_Router name=R1 ++ovn-nbctl create Logical_Router name=R2 options:chassis=hv1 ++ ++ovn-nbctl ls-add foo ++ovn-nbctl ls-add bar ++ovn-nbctl ls-add alice ++ovn-nbctl ls-add join ++ ++# Connect foo to R1 ++ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 ++ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ ++ type=router options:router-port=foo addresses=\"00:00:01:01:02:03\" ++ ++# Connect bar to R1 ++ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24 ++ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \ ++ type=router options:router-port=bar addresses=\"00:00:01:01:02:04\" ++ ++# Connect alice to R2 ++ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24 ++ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ ++ type=router options:router-port=alice addresses=\"00:00:02:01:02:03\" ++ ++# Connect R1 to join ++ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24 ++ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \ ++ type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"' ++ ++# Connect R2 to join ++ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24 ++ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \ ++ type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"' ++ ++# Static routes. ++ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2 ++ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1 ++ ++# Logical port 'foo1' in switch 'foo'. ++ADD_NAMESPACES(foo1) ++ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \ ++ "192.168.1.1") ++ovn-nbctl lsp-add foo foo1 \ ++-- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" ++ ++# Logical port 'alice1' in switch 'alice'. ++ADD_NAMESPACES(alice1) ++ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:04", \ ++ "172.16.1.1") ++ovn-nbctl lsp-add alice alice1 \ ++-- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2" ++ ++# Logical port 'bar1' in switch 'bar'. ++ADD_NAMESPACES(bar1) ++ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \ ++"192.168.2.1") ++ovn-nbctl lsp-add bar bar1 \ ++-- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2" ++ ++# Config OVN load-balancer with a VIP. ++uuid=`ovn-nbctl create load_balancer protocol=sctp vips:30.0.0.1="192.168.1.2,192.168.2.2"` ++ovn-nbctl set logical_router R2 load_balancer=$uuid ++ ++# Config OVN load-balancer with another VIP (this time with ports). ++ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:12345,192.168.2.2:12345"' ++ ++# Add SNAT rule to make sure that Load-balancing still works with a SNAT rule. ++ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \ ++ external_ip=30.0.0.2 -- add logical_router R2 nat @nat ++ ++# Wait for ovn-controller to catch up. ++ovn-nbctl --wait=hv sync ++OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \ ++grep 'nat(dst=192.168.2.2:12345)']) ++ ++# Start webservers in 'foo1', 'bar1'. ++OVS_START_L7([foo1], [sctp]) ++OVS_START_L7([bar1], [sctp]) ++ ++on_exit "ovs-ofctl -O OpenFlow13 dump-flows br-int" ++ ++dnl Should work with the virtual IP address through NAT ++for i in `seq 1 20`; do ++ echo Request $i ++ NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.1 12345 > client$i.log]) ++done ++ ++dnl Each server should have at least one connection. ++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | ++sed -e 's/zone=[[0-9]]*/zone=/' | ++sed 's/,protoinfo=.*$//' | uniq], [0], [dnl ++sctp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=,dport=),reply=(src=192.168.1.2,dst=172.16.1.2,sport=,dport=),zone=,mark=2 ++sctp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=,dport=),reply=(src=192.168.2.2,dst=172.16.1.2,sport=,dport=),zone=,mark=2 ++]) ++ ++dnl Test load-balancing that includes L4 ports in NAT. ++for i in `seq 1 20`; do ++ echo Request $i ++ NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.2 8000 > clients$i.log]) ++done ++ ++dnl Each server should have at least one connection. ++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | ++sed -e 's/zone=[[0-9]]*/zone=/' | ++sed 's/,protoinfo=.*$//' | uniq], [0], [dnl ++sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.1.2,dst=172.16.1.2,sport=,dport=),zone=,mark=2 ++sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.2.2,dst=172.16.1.2,sport=,dport=),zone=,mark=2 ++]) ++ ++check_est_flows () { ++ n=$(ovs-ofctl dump-flows br-int table=15 | grep "+est" \ ++ | grep "ct_mark=$1" | sed -n 's/.*n_packets=\([[0-9]]\{1,\}\).*/\1/p') ++ ++ echo "n_packets=$n" ++ test -n "$n" && test "$n" != "0" ++} ++ ++OVS_WAIT_UNTIL([check_est_flows 0x2], [check established flows]) ++ ++ ++ovn-nbctl set logical_router R2 options:lb_force_snat_ip="20.0.0.2" ++ ++# Destroy the load balancer and create again. ovn-controller will ++# clear the OF flows and re add again and clears the n_packets ++# for these flows. ++ovn-nbctl destroy load_balancer $uuid ++uuid=`ovn-nbctl create load_balancer protocol=sctp vips:30.0.0.1="192.168.1.2,192.168.2.2"` ++ovn-nbctl set logical_router R2 load_balancer=$uuid ++ ++check ovs-appctl dpctl/flush-conntrack ++ ++# Config OVN load-balancer with another VIP (this time with ports). ++ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:12345,192.168.2.2:12345"' ++ ++ovn-nbctl list load_balancer ++ovn-sbctl dump-flows R2 ++OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=45 | grep 'nat(src=20.0.0.2)']) ++ ++dnl Test load-balancing that includes L4 ports in NAT. ++for i in `seq 1 20`; do ++ echo Request $i ++ NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.2 8000 > clients$i.log]) ++done ++ ++dnl Each server should have at least one connection. ++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | ++sed -e 's/zone=[[0-9]]*/zone=/' | ++sed 's/,protoinfo=.*$//' | uniq], [0], [dnl ++sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.1.2,dst=172.16.1.2,sport=,dport=),zone=,mark=10 ++sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.2.2,dst=172.16.1.2,sport=,dport=),zone=,mark=10 ++]) ++ ++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) | ++sed -e 's/zone=[[0-9]]*/zone=/' | ++sed 's/,protoinfo=.*$//' | uniq], [0], [dnl ++sctp,orig=(src=172.16.1.2,dst=192.168.1.2,sport=,dport=),reply=(src=192.168.1.2,dst=20.0.0.2,sport=,dport=),zone= ++sctp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=,dport=),reply=(src=192.168.2.2,dst=20.0.0.2,sport=,dport=),zone= ++]) ++ ++OVS_WAIT_UNTIL([check_est_flows 0xa], [check established flows]) ++ ++OVS_APP_EXIT_AND_WAIT([ovn-controller]) ++ ++as ovn-sb ++OVS_APP_EXIT_AND_WAIT([ovsdb-server]) ++ ++as ovn-nb ++OVS_APP_EXIT_AND_WAIT([ovsdb-server]) ++ ++as northd ++OVS_APP_EXIT_AND_WAIT([ovn-northd]) ++ ++as ++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d ++/connection dropped.*/d"]) ++AT_CLEANUP ++ ++OVN_FOR_EACH_NORTHD([ ++AT_SETUP([load balancing affinity sessions - auto clear learnt flows]) ++AT_SKIP_IF([test $HAVE_NC = no]) ++AT_KEYWORDS([lb]) ++ ++ovn_start ++OVS_TRAFFIC_VSWITCHD_START() ++ADD_BR([br-int]) ++ ++check ovs-vsctl \ ++ -- set Open_vSwitch . external-ids:system-id=hv1 \ ++ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ ++ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ ++ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ ++ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true ++ ++start_daemon ovn-controller ++ ++check ovn-nbctl lr-add lr ++check ovn-nbctl lrp-add lr lr-ls 00:00:00:00:01:00 42.42.42.3/24 ++check ovn-nbctl ls-add ls ++ ++check ovn-nbctl lsp-add ls ls-lr ++check ovn-nbctl lsp-set-addresses ls-lr 00:00:00:00:01:00 ++check ovn-nbctl lsp-set-type ls-lr router ++check ovn-nbctl lsp-set-options ls-lr router-port=lr-ls ++check ovn-nbctl lsp-add ls vm1 ++check ovn-nbctl lsp-set-addresses vm1 00:00:00:00:00:01 ++check ovn-nbctl lsp-add ls vm2 ++check ovn-nbctl lsp-set-addresses vm2 00:00:00:00:00:02 ++check ovn-nbctl lb-add lb-test 43.43.43.43:80 42.42.42.1:8080,42.42.42.2:8080 tcp \ ++ -- set load_balancer lb-test options:affinity_timeout=65535 \ ++ -- ls-lb-add ls lb-test ++ ++dnl Start a server on vm1. ++ADD_NAMESPACES(vm1) ++ADD_VETH(vm1, vm1, br-int, "42.42.42.1/24", "00:00:00:00:00:01", "42.42.42.3") ++NETNS_DAEMONIZE([vm1], [nc -l -k 42.42.42.1 8080], [vm1.pid]) ++ ++dnl Start a server on vm2. ++ADD_NAMESPACES(vm2) ++ADD_VETH(vm2, vm2, br-int, "42.42.42.2/24", "00:00:00:00:00:02", "42.42.42.3") ++NETNS_DAEMONIZE([vm2], [nc -l -k 42.42.42.2 8080], [vm2.pid]) ++ ++dnl Wait for ovn-controller to catch up. ++wait_for_ports_up ++check ovn-nbctl --wait=hv sync ++ ++dnl Test the connection. ++OVS_WAIT_UNTIL([ ++ ip netns exec vm1 nc -z 43.43.43.43 80 &> /dev/null ++]) ++ ++OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int | grep 'table=78, n_packets' -c) -eq 1]) ++ ++dnl Find the backend that was hit. ++backend=$(ovs-ofctl dump-flows br-int table=78 | \ ++ grep -oE 'load:0x2a2a2a0[[12]]' | sed -n 's/load:0x2a2a2a0\(.*\)/\1/p') ++ ++dnl Remove the backend that was hit. ++if [[ "$backend" == "1" ]]; then ++ check ovn-nbctl set load_balancer lb-test vip:\"43.43.43.43:80\"=\"42.42.42.2:8080\" ++else ++ check ovn-nbctl set load_balancer lb-test vip:\"43.43.43.43:80\"=\"42.42.42.1:8080\" ++fi ++check ovn-nbctl --wait=hv sync ++ ++dnl The learnt flow should also be auto deleted. ++AT_CHECK([ovs-ofctl dump-flows br-int | grep 'table=78, n_packets' -c], [1], [dnl ++0 ++]) ++ ++OVS_APP_EXIT_AND_WAIT([ovn-controller]) ++ ++as ovn-sb ++OVS_APP_EXIT_AND_WAIT([ovsdb-server]) ++ ++as ovn-nb ++OVS_APP_EXIT_AND_WAIT([ovsdb-server]) ++ ++as northd ++OVS_APP_EXIT_AND_WAIT([ovn-northd]) ++ ++as ++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d ++/connection dropped.*/d"]) ++AT_CLEANUP ++]) +diff --git a/tests/test-ovn.c b/tests/test-ovn.c +index 1f1e27b51..aaf2825ed 100644 +--- a/tests/test-ovn.c ++++ b/tests/test-ovn.c +@@ -1300,11 +1300,11 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED) + + /* Initialize group ids. */ + struct ovn_extend_table group_table; +- ovn_extend_table_init(&group_table); ++ ovn_extend_table_init(&group_table, "group-table", OFPG_MAX); + + /* Initialize meter ids for QoS. */ + struct ovn_extend_table meter_table; +- ovn_extend_table_init(&meter_table); ++ ovn_extend_table_init(&meter_table, "meter-table", OFPM13_MAX); + + /* Initialize collector sets. */ + struct flow_collector_ids collector_ids; +diff --git a/utilities/checkpatch.py b/utilities/checkpatch.py +index 5467d604d..52d3fa845 100755 +--- a/utilities/checkpatch.py ++++ b/utilities/checkpatch.py +@@ -40,6 +40,15 @@ MAX_LINE_LEN = 79 + def open_spell_check_dict(): + import enchant + ++ try: ++ import codespell_lib ++ codespell_dir = os.path.dirname(codespell_lib.__file__) ++ codespell_file = os.path.join(codespell_dir, 'data', 'dictionary.txt') ++ if not os.path.exists(codespell_file): ++ codespell_file = '' ++ except: ++ codespell_file = '' ++ + try: + extra_keywords = ['ovs', 'vswitch', 'vswitchd', 'ovs-vswitchd', + 'netdev', 'selinux', 'ovs-ctl', 'dpctl', 'ofctl', +@@ -92,9 +101,18 @@ def open_spell_check_dict(): + 'syscall', 'lacp', 'ipf', 'skb', 'valgrind'] + + global spell_check_dict ++ + spell_check_dict = enchant.Dict("en_US") ++ ++ if codespell_file: ++ with open(codespell_file) as f: ++ for line in f.readlines(): ++ words = line.strip().split('>')[1].strip(', ').split(',') ++ for word in words: ++ spell_check_dict.add_to_session(word.strip()) ++ + for kw in extra_keywords: +- spell_check_dict.add(kw) ++ spell_check_dict.add_to_session(kw) + + return True + except: +@@ -190,6 +208,7 @@ skip_trailing_whitespace_check = False + skip_gerrit_change_id_check = False + skip_block_whitespace_check = False + skip_signoff_check = False ++skip_committer_signoff_check = False + + # Don't enforce character limit on files that include these characters in their + # name, as they may have legitimate reasons to have longer lines. +@@ -282,9 +301,13 @@ def if_and_for_end_with_bracket_check(line): + if len(line) == MAX_LINE_LEN - 1 and line[-1] == ')': + return True + +- if __regex_ends_with_bracket.search(line) is None and \ +- __regex_if_macros.match(line) is None: +- return False ++ if __regex_ends_with_bracket.search(line) is None: ++ if line.endswith("\\") and \ ++ __regex_if_macros.match(line) is not None: ++ return True ++ else: ++ return False ++ + if __regex_conditional_else_bracing.match(line) is not None: + return False + if __regex_conditional_else_bracing2.match(line) is not None: +@@ -410,9 +433,15 @@ def check_spelling(line, comment): + if not spell_check_dict or not spellcheck: + return False + ++ if line.startswith('Fixes: '): ++ return False ++ + words = filter_comments(line, True) if comment else line + words = words.replace(':', ' ').split(' ') + ++ flagged_words = [] ++ num_suggestions = 3 ++ + for word in words: + skip = False + strword = re.subn(r'\W+', '', word)[0].replace(',', '') +@@ -437,9 +466,15 @@ def check_spelling(line, comment): + skip = True + + if not skip: +- print_warning("Check for spelling mistakes (e.g. \"%s\")" +- % strword) +- return True ++ flagged_words.append(strword) ++ ++ if len(flagged_words) > 0: ++ for mistake in flagged_words: ++ print_warning("Possible misspelled word: \"%s\"" % mistake) ++ print("Did you mean: ", ++ spell_check_dict.suggest(mistake)[:num_suggestions]) ++ ++ return True + + return False + +@@ -780,6 +815,36 @@ def run_file_checks(text): + check['check'](text) + + ++def run_subject_checks(subject, spellcheck=False): ++ warnings = False ++ ++ if spellcheck and check_spelling(subject, False): ++ warnings = True ++ ++ summary = subject[subject.rindex(': ') + 2:] ++ area_summary = subject[subject.index(': ') + 2:] ++ area_summary_len = len(area_summary) ++ if area_summary_len > 70: ++ print_warning("The subject, ': ', is over 70 " ++ "characters, i.e., %u." % area_summary_len) ++ warnings = True ++ ++ if summary[0].isalpha() and summary[0].islower(): ++ print_warning( ++ "The subject summary should start with a capital.") ++ warnings = True ++ ++ if subject[-1] not in [".", "?", "!"]: ++ print_warning( ++ "The subject summary should end with a dot.") ++ warnings = True ++ ++ if warnings: ++ print(subject) ++ ++ return warnings ++ ++ + def ovs_checkpatch_parse(text, filename, author=None, committer=None): + global print_file_name, total_line, checking_file, \ + empty_return_check_state +@@ -800,6 +865,7 @@ def ovs_checkpatch_parse(text, filename, author=None, committer=None): + r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@') + is_author = re.compile(r'^(Author|From): (.*)$', re.I | re.M | re.S) + is_committer = re.compile(r'^(Commit: )(.*)$', re.I | re.M | re.S) ++ is_subject = re.compile(r'^(Subject: )(.*)$', re.I | re.M | re.S) + is_signature = re.compile(r'^(Signed-off-by: )(.*)$', + re.I | re.M | re.S) + is_co_author = re.compile(r'^(Co-authored-by: )(.*)$', +@@ -874,7 +940,8 @@ def ovs_checkpatch_parse(text, filename, author=None, committer=None): + break + if (committer + and author != committer +- and committer not in signatures): ++ and committer not in signatures ++ and not skip_committer_signoff_check): + print_error("Committer %s needs to sign off." + % committer) + +@@ -899,6 +966,8 @@ def ovs_checkpatch_parse(text, filename, author=None, committer=None): + committer = is_committer.match(line).group(2) + elif is_author.match(line): + author = is_author.match(line).group(2) ++ elif is_subject.match(line): ++ run_subject_checks(line, spellcheck) + elif is_signature.match(line): + m = is_signature.match(line) + signatures.append(m.group(2)) +@@ -990,7 +1059,8 @@ Check options: + -S|--spellcheck Check C comments and commit-message for possible + spelling mistakes + -t|--skip-trailing-whitespace Skips the trailing whitespace test +- --skip-gerrit-change-id Skips the gerrit change id test""" ++ --skip-gerrit-change-id Skips the gerrit change id test ++ --skip-committer-signoff Skips the committer sign-off test""" + % sys.argv[0]) + + +@@ -1017,6 +1087,19 @@ def ovs_checkpatch_file(filename): + result = ovs_checkpatch_parse(part.get_payload(decode=False), filename, + mail.get('Author', mail['From']), + mail['Commit']) ++ ++ if not mail['Subject'] or not mail['Subject'].strip(): ++ if mail['Subject']: ++ mail.replace_header('Subject', sys.argv[-1]) ++ else: ++ mail.add_header('Subject', sys.argv[-1]) ++ ++ print("Subject missing! Your provisional subject is", ++ mail['Subject']) ++ ++ if run_subject_checks('Subject: ' + mail['Subject'], spellcheck): ++ result = True ++ + ovs_checkpatch_print_result() + return result + +@@ -1048,6 +1131,7 @@ if __name__ == '__main__': + "skip-signoff-lines", + "skip-trailing-whitespace", + "skip-gerrit-change-id", ++ "skip-committer-signoff", + "spellcheck", + "quiet"]) + except: +@@ -1068,6 +1152,8 @@ if __name__ == '__main__': + skip_trailing_whitespace_check = True + elif o in ("--skip-gerrit-change-id"): + skip_gerrit_change_id_check = True ++ elif o in ("--skip-committer-signoff"): ++ skip_committer_signoff_check = True + elif o in ("-f", "--check-file"): + checking_file = True + elif o in ("-S", "--spellcheck"): +diff --git a/utilities/containers/fedora/Dockerfile b/utilities/containers/fedora/Dockerfile +index 4058d7f5b..c11ea37b7 100755 +--- a/utilities/containers/fedora/Dockerfile ++++ b/utilities/containers/fedora/Dockerfile +@@ -1,4 +1,4 @@ +-FROM quay.io/fedora/fedora:latest ++FROM quay.io/fedora/fedora:38 + + ARG CONTAINERS_PATH + +diff --git a/utilities/containers/py-requirements.txt b/utilities/containers/py-requirements.txt +index 0d90765c9..aac98443b 100644 +--- a/utilities/containers/py-requirements.txt ++++ b/utilities/containers/py-requirements.txt +@@ -1,5 +1,4 @@ +-flake8 +-hacking>=3.0 ++flake8==5.0.4 + scapy + sphinx + setuptools +diff --git a/utilities/containers/ubuntu/Dockerfile b/utilities/containers/ubuntu/Dockerfile +index 5d5bedbd9..3c7fe7775 100755 +--- a/utilities/containers/ubuntu/Dockerfile ++++ b/utilities/containers/ubuntu/Dockerfile +@@ -1,4 +1,4 @@ +-FROM registry.hub.docker.com/library/ubuntu:latest ++FROM registry.hub.docker.com/library/ubuntu:22.04 + + ARG CONTAINERS_PATH + +diff --git a/utilities/ovn-ctl.8.xml b/utilities/ovn-ctl.8.xml +index 82804096f..01f4aa26b 100644 +--- a/utilities/ovn-ctl.8.xml ++++ b/utilities/ovn-ctl.8.xml +@@ -136,12 +136,14 @@ +

      --db-nb-cluster-remote-addr=IP ADDRESS

      +

      --db-nb-cluster-remote-port=PORT NUMBER

      +

      --db-nb-cluster-remote-proto=PROTO (tcp/ssl)

      ++

      --db-nb-election-timer=Timeout in milliseconds

      +

      --db-sb-cluster-local-addr=IP ADDRESS

      +

      --db-sb-cluster-local-port=PORT NUMBER

      +

      --db-sb-cluster-local-proto=PROTO (tcp/ssl)

      +

      --db-sb-cluster-remote-addr=IP ADDRESS

      +

      --db-sb-cluster-remote-port=PORT NUMBER

      +

      --db-sb-cluster-remote-proto=PROTO (tcp/ssl)

      ++

      --db-sb-election-timer=Timeout in milliseconds

      +

      --db-ic-nb-cluster-local-addr=IP ADDRESS

      +

      --db-ic-nb-cluster-local-port=PORT NUMBER

      +

      --db-ic-nb-cluster-local-proto=PROTO (tcp/ssl)

      +diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c +index 444fbd2fe..821ad44dd 100644 +--- a/utilities/ovn-nbctl.c ++++ b/utilities/ovn-nbctl.c +@@ -4693,6 +4693,7 @@ static void + nexthop = normalize_prefix_str(ctx->argv[3]); + if (!nexthop) { + ctl_error(ctx, "bad nexthop argument: %s", ctx->argv[3]); ++ free(prefix); + return; + } + } +diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c +index 3948cae3f..f1f8c2b42 100644 +--- a/utilities/ovn-sbctl.c ++++ b/utilities/ovn-sbctl.c +@@ -396,7 +396,9 @@ pre_get_info(struct ctl_context *ctx) + ovsdb_idl_add_column(ctx->idl, &sbrec_mac_binding_col_mac); + + ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_datapaths); ++ /* datapath_group column is deprecated. */ + ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_datapath_group); ++ ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_ls_datapath_group); + ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_vips); + ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_name); + ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_protocol); +@@ -932,10 +934,15 @@ cmd_lflow_list_load_balancers(struct ctl_context *ctx, struct vconn *vconn, + break; + } + } ++ /* datapath_group column is deprecated. */ + if (lb->datapath_group && !dp_found) { + dp_found = datapath_group_contains_datapath(lb->datapath_group, + datapath); + } ++ if (lb->ls_datapath_group && !dp_found) { ++ dp_found = datapath_group_contains_datapath( ++ lb->ls_datapath_group, datapath); ++ } + if (!dp_found) { + continue; + } +@@ -954,11 +961,17 @@ cmd_lflow_list_load_balancers(struct ctl_context *ctx, struct vconn *vconn, + print_vflow_datapath_name(lb->datapaths[i], true, + &ctx->output); + } ++ /* datapath_group column is deprecated. */ + for (size_t i = 0; lb->datapath_group + && i < lb->datapath_group->n_datapaths; i++) { + print_vflow_datapath_name(lb->datapath_group->datapaths[i], + true, &ctx->output); + } ++ for (size_t i = 0; lb->ls_datapath_group ++ && i < lb->ls_datapath_group->n_datapaths; i++) { ++ print_vflow_datapath_name(lb->ls_datapath_group->datapaths[i], ++ true, &ctx->output); ++ } + } + + ds_put_cstr(&ctx->output, "\n vips:\n"); +diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c +index 0b86eae7b..13ae464ad 100644 +--- a/utilities/ovn-trace.c ++++ b/utilities/ovn-trace.c +@@ -983,6 +983,7 @@ parse_lflow_for_datapath(const struct sbrec_logical_flow *sblf, + if (error) { + VLOG_WARN("%s: parsing expression failed (%s)", + sblf->match, error); ++ expr_destroy(match); + free(error); + return; + } diff --git a/SPECS/ovn23.06.spec b/SPECS/ovn23.06.spec deleted file mode 100644 index 9a9a793..0000000 --- a/SPECS/ovn23.06.spec +++ /dev/null @@ -1,560 +0,0 @@ -# Copyright (C) 2009, 2010, 2013, 2014 Nicira Networks, Inc. -# -# Copying and distribution of this file, with or without modification, -# are permitted in any medium without royalty provided the copyright -# notice and this notice are preserved. This file is offered as-is, -# without warranty of any kind. -# -# If tests have to be skipped while building, specify the '--without check' -# option. For example: -# rpmbuild -bb --without check rhel/openvswitch-fedora.spec - -# This defines the base package name's version. - -%define pkgver 2.13 -%define pkgname ovn23.06 - -# If libcap-ng isn't available and there is no need for running OVS -# as regular user, specify the '--without libcapng' -%bcond_without libcapng - -# Enable PIE, bz#955181 -%global _hardened_build 1 - -# RHEL-7 doesn't define _rundir macro yet -# Fedora 15 onwards uses /run as _rundir -%if 0%{!?_rundir:1} -%define _rundir /run -%endif - -# Build python2 (that provides python) and python3 subpackages on Fedora -# Build only python3 (that provides python) subpackage on RHEL8 -# Build only python subpackage on RHEL7 -%if 0%{?rhel} > 7 || 0%{?fedora} -# On RHEL8 Sphinx is included in buildroot -%global external_sphinx 1 -%else -# Don't use external sphinx (RHV doesn't have optional repositories enabled) -%global external_sphinx 0 -%endif - -# We would see rpmlinit error - E: hardcoded-library-path in '% {_prefix}/lib'. -# But there is no solution to fix this. Using {_lib} macro will solve the -# rpmlink error, but will install the files in /usr/lib64/. -# OVN pacemaker ocf script file is copied in /usr/lib/ocf/resource.d/ovn/ -# and we are not sure if pacemaker looks into this path to find the -# OVN resource agent script. -%global ovnlibdir %{_prefix}/lib - -Name: %{pkgname} -Summary: Open Virtual Network support -Group: System Environment/Daemons -URL: http://www.ovn.org/ -Version: 23.06.1 -Release: 11%{?commit0:.%{date}git%{shortcommit0}}%{?dist} -Provides: openvswitch%{pkgver}-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release} -Obsoletes: openvswitch%{pkgver}-ovn-common < 2.11.0-1 - -# Nearly all of openvswitch is ASL 2.0. The bugtool is LGPLv2+, and the -# lib/sflow*.[ch] files are SISSL -License: ASL 2.0 and LGPLv2+ and SISSL - -%define ovncommit a20f880efdba9dcf19c1df77b31a3b8b9dffa345 - -# Always pull an upstream release, since this is what we rebase to. -Source: https://github.com/ovn-org/ovn/archive/%{ovncommit}.tar.gz#/ovn-%{version}.tar.gz - -%define ovscommit 0187eadfce4505d502e57c0e688b830f0a1ec728 -%define ovsshortcommit 0187ead - -Source10: https://github.com/openvswitch/ovs/archive/%{ovscommit}.tar.gz#/openvswitch-%{ovsshortcommit}.tar.gz -%define ovsdir ovs-%{ovscommit} - -%define docutilsver 0.12 -%define pygmentsver 1.4 -%define sphinxver 1.1.3 -Source100: https://pypi.io/packages/source/d/docutils/docutils-%{docutilsver}.tar.gz -Source101: https://pypi.io/packages/source/P/Pygments/Pygments-%{pygmentsver}.tar.gz -Source102: https://pypi.io/packages/source/S/Sphinx/Sphinx-%{sphinxver}.tar.gz - -Source500: configlib.sh -Source501: gen_config_group.sh -Source502: set_config.sh - -# Important: source503 is used as the actual copy file -# @TODO: this causes a warning - fix it? -Source504: arm64-armv8a-linuxapp-gcc-config -Source505: ppc_64-power8-linuxapp-gcc-config -Source506: x86_64-native-linuxapp-gcc-config - -Patch: %{pkgname}.patch - -# FIXME Sphinx is used to generate some manpages, unfortunately, on RHEL, it's -# in the -optional repository and so we can't require it directly since RHV -# doesn't have the -optional repository enabled and so TPS fails -%if %{external_sphinx} -BuildRequires: python3-sphinx -%else -# Sphinx dependencies -BuildRequires: python-devel -BuildRequires: python-setuptools -#BuildRequires: python2-docutils -BuildRequires: python-jinja2 -BuildRequires: python-nose -#BuildRequires: python2-pygments -# docutils dependencies -BuildRequires: python-imaging -# pygments dependencies -BuildRequires: python-nose -%endif - -BuildRequires: gcc gcc-c++ make -BuildRequires: autoconf automake libtool -BuildRequires: systemd-units openssl openssl-devel -BuildRequires: python3-devel python3-setuptools -BuildRequires: desktop-file-utils -BuildRequires: groff-base graphviz -BuildRequires: unbound-devel - -# make check dependencies -BuildRequires: procps-ng -%if 0%{?rhel} == 8 || 0%{?fedora} -BuildRequires: python3-pyOpenSSL -%endif -BuildRequires: tcpdump - -%if %{with libcapng} -BuildRequires: libcap-ng libcap-ng-devel -%endif - -Requires: hostname openssl iproute module-init-tools - -Requires(post): systemd-units -Requires(preun): systemd-units -Requires(postun): systemd-units - -# to skip running checks, pass --without check -%bcond_without check - -%description -OVN, the Open Virtual Network, is a system to support virtual network -abstraction. OVN complements the existing capabilities of OVS to add -native support for virtual network abstractions, such as virtual L2 and L3 -overlays and security groups. - -%package central -Summary: Open Virtual Network support -License: ASL 2.0 -Requires: %{pkgname} -Requires: firewalld-filesystem -Provides: openvswitch%{pkgver}-ovn-central = %{?epoch:%{epoch}:}%{version}-%{release} -Obsoletes: openvswitch%{pkgver}-ovn-central < 2.11.0-1 - -%description central -OVN DB servers and ovn-northd running on a central node. - -%package host -Summary: Open Virtual Network support -License: ASL 2.0 -Requires: %{pkgname} -Requires: firewalld-filesystem -Provides: openvswitch%{pkgver}-ovn-host = %{?epoch:%{epoch}:}%{version}-%{release} -Obsoletes: openvswitch%{pkgver}-ovn-host < 2.11.0-1 - -%description host -OVN controller running on each host. - -%package vtep -Summary: Open Virtual Network support -License: ASL 2.0 -Requires: %{pkgname} -Provides: openvswitch%{pkgver}-ovn-vtep = %{?epoch:%{epoch}:}%{version}-%{release} -Obsoletes: openvswitch%{pkgver}-ovn-vtep < 2.11.0-1 - -%description vtep -OVN vtep controller - -%prep -%autosetup -n ovn-%{ovncommit} -a 10 -p 1 - -%build -%if 0%{?commit0:1} -# fix the snapshot unreleased version to be the released one. -sed -i.old -e "s/^AC_INIT(openvswitch,.*,/AC_INIT(openvswitch, %{version},/" configure.ac -%endif -./boot.sh - -# OVN source code is now separate. -# Build openvswitch first. -# XXX Current openvswitch2.13 doesn't -# use "2.13.0" for version. It's a commit hash -pushd %{ovsdir} -./boot.sh -%configure \ -%if %{with libcapng} - --enable-libcapng \ -%else - --disable-libcapng \ -%endif - --enable-ssl \ - --with-pkidir=%{_sharedstatedir}/openvswitch/pki - -make %{?_smp_mflags} -popd - -# Build OVN. -# XXX OVS version needs to be updated when ovs2.13 is updated. -%configure \ - --with-ovs-source=$PWD/%{ovsdir} \ -%if %{with libcapng} - --enable-libcapng \ -%else - --disable-libcapng \ -%endif - --enable-ssl \ - --with-pkidir=%{_sharedstatedir}/openvswitch/pki - -make %{?_smp_mflags} - -%install -%make_install -install -p -D -m 0644 \ - rhel/usr_share_ovn_scripts_systemd_sysconfig.template \ - $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/ovn - -for service in ovn-controller ovn-controller-vtep ovn-northd; do - install -p -D -m 0644 \ - rhel/usr_lib_systemd_system_${service}.service \ - $RPM_BUILD_ROOT%{_unitdir}/${service}.service -done - -install -d -m 0755 $RPM_BUILD_ROOT/%{_sharedstatedir}/ovn - -install -d $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/ -install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml \ - $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/ovn-central-firewall-service.xml -install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml \ - $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/ovn-host-firewall-service.xml - -install -d -m 0755 $RPM_BUILD_ROOT%{ovnlibdir}/ocf/resource.d/ovn -ln -s %{_datadir}/ovn/scripts/ovndb-servers.ocf \ - $RPM_BUILD_ROOT%{ovnlibdir}/ocf/resource.d/ovn/ovndb-servers - -install -p -D -m 0644 rhel/etc_logrotate.d_ovn \ - $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.d/ovn - -# remove unneeded files. -rm -f $RPM_BUILD_ROOT%{_bindir}/ovs* -rm -f $RPM_BUILD_ROOT%{_bindir}/vtep-ctl -rm -f $RPM_BUILD_ROOT%{_sbindir}/ovs* -rm -f $RPM_BUILD_ROOT%{_mandir}/man1/ovs* -rm -f $RPM_BUILD_ROOT%{_mandir}/man5/ovs* -rm -f $RPM_BUILD_ROOT%{_mandir}/man5/vtep* -rm -f $RPM_BUILD_ROOT%{_mandir}/man7/ovs* -rm -f $RPM_BUILD_ROOT%{_mandir}/man8/ovs* -rm -f $RPM_BUILD_ROOT%{_mandir}/man8/vtep* -rm -rf $RPM_BUILD_ROOT%{_datadir}/ovn/python -rm -f $RPM_BUILD_ROOT%{_datadir}/ovn/scripts/ovs* -rm -rf $RPM_BUILD_ROOT%{_datadir}/ovn/bugtool-plugins -rm -f $RPM_BUILD_ROOT%{_libdir}/*.a -rm -f $RPM_BUILD_ROOT%{_libdir}/*.la -rm -f $RPM_BUILD_ROOT%{_libdir}/pkgconfig/*.pc -rm -f $RPM_BUILD_ROOT%{_includedir}/ovn/* -rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-appctl-bashcomp.bash -rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-vsctl-bashcomp.bash -rm -rf $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/openvswitch -rm -f $RPM_BUILD_ROOT%{_datadir}/ovn/scripts/ovn-bugtool* -rm -f $RPM_BUILD_ROOT/%{_bindir}/ovn-docker-overlay-driver \ - $RPM_BUILD_ROOT/%{_bindir}/ovn-docker-underlay-driver - -%check -%if %{with check} - touch resolv.conf - export OVS_RESOLV_CONF=$(pwd)/resolv.conf - if ! make check TESTSUITEFLAGS='%{_smp_mflags}'; then - cat tests/testsuite.log - if ! make check TESTSUITEFLAGS='--recheck'; then - cat tests/testsuite.log - # Presently a test case - "2796: ovn -- ovn-controller incremental processing" - # is failing on aarch64 arch. Let's not exit for this arch - # until we figure out why it is failing. - # Test case 93: ovn.at:12105 ovn -- ACLs on Port Groups is failing - # repeatedly on s390x. This needs to be investigated. - %ifnarch aarch64 - %ifnarch ppc64le - %ifnarch s390x - exit 1 - %endif - %endif - %endif - fi - fi -%endif - -%clean -rm -rf $RPM_BUILD_ROOT - -%pre central -if [ $1 -eq 1 ] ; then - # Package install. - /bin/systemctl status ovn-northd.service >/dev/null - ovn_status=$? - rpm -ql openvswitch-ovn-central > /dev/null - if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then - # ovn-northd service is running which means old openvswitch-ovn-central - # is already installed and it will be cleaned up. So start ovn-northd - # service when posttrans central is called. - touch %{_localstatedir}/lib/rpm-state/ovn-northd - fi -fi - -%pre host -if [ $1 -eq 1 ] ; then - # Package install. - /bin/systemctl status ovn-controller.service >/dev/null - ovn_status=$? - rpm -ql openvswitch-ovn-host > /dev/null - if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then - # ovn-controller service is running which means old - # openvswitch-ovn-host is installed and it will be cleaned up. So - # start ovn-controller service when posttrans host is called. - touch %{_localstatedir}/lib/rpm-state/ovn-controller - fi -fi - -%pre vtep -if [ $1 -eq 1 ] ; then - # Package install. - /bin/systemctl status ovn-controller-vtep.service >/dev/null - ovn_status=$? - rpm -ql openvswitch-ovn-vtep > /dev/null - if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then - # ovn-controller-vtep service is running which means old - # openvswitch-ovn-vtep is installed and it will be cleaned up. So - # start ovn-controller-vtep service when posttrans host is called. - touch %{_localstatedir}/lib/rpm-state/ovn-controller-vtep - fi -fi - -%preun central -%if 0%{?systemd_preun:1} - %systemd_preun ovn-northd.service -%else - if [ $1 -eq 0 ] ; then - # Package removal, not upgrade - /bin/systemctl --no-reload disable ovn-northd.service >/dev/null 2>&1 || : - /bin/systemctl stop ovn-northd.service >/dev/null 2>&1 || : - fi -%endif - -%preun host -%if 0%{?systemd_preun:1} - %systemd_preun ovn-controller.service -%else - if [ $1 -eq 0 ] ; then - # Package removal, not upgrade - /bin/systemctl --no-reload disable ovn-controller.service >/dev/null 2>&1 || : - /bin/systemctl stop ovn-controller.service >/dev/null 2>&1 || : - fi -%endif - -%preun vtep -%if 0%{?systemd_preun:1} - %systemd_preun ovn-controller-vtep.service -%else - if [ $1 -eq 0 ] ; then - # Package removal, not upgrade - /bin/systemctl --no-reload disable ovn-controller-vtep.service >/dev/null 2>&1 || : - /bin/systemctl stop ovn-controller-vtep.service >/dev/null 2>&1 || : - fi -%endif - -%post -%if %{with libcapng} -if [ $1 -eq 1 ]; then - sed -i 's:^#OVN_USER_ID=:OVN_USER_ID=:' %{_sysconfdir}/sysconfig/ovn - sed -i 's:\(.*su\).*:\1 openvswitch openvswitch:' %{_sysconfdir}/logrotate.d/ovn -fi -%endif - -%post central -%if 0%{?systemd_post:1} - %systemd_post ovn-northd.service -%else - # Package install, not upgrade - if [ $1 -eq 1 ]; then - /bin/systemctl daemon-reload >dev/null || : - fi -%endif - -%post host -%if 0%{?systemd_post:1} - %systemd_post ovn-controller.service -%else - # Package install, not upgrade - if [ $1 -eq 1 ]; then - /bin/systemctl daemon-reload >dev/null || : - fi -%endif - -%post vtep -%if 0%{?systemd_post:1} - %systemd_post ovn-controller-vtep.service -%else - # Package install, not upgrade - if [ $1 -eq 1 ]; then - /bin/systemctl daemon-reload >dev/null || : - fi -%endif - -%postun - -%postun central -%if 0%{?systemd_postun_with_restart:1} - %systemd_postun_with_restart ovn-northd.service -%else - /bin/systemctl daemon-reload >/dev/null 2>&1 || : - if [ "$1" -ge "1" ] ; then - # Package upgrade, not uninstall - /bin/systemctl try-restart ovn-northd.service >/dev/null 2>&1 || : - fi -%endif - -%postun host -%if 0%{?systemd_postun_with_restart:1} - %systemd_postun_with_restart ovn-controller.service -%else - /bin/systemctl daemon-reload >/dev/null 2>&1 || : - if [ "$1" -ge "1" ] ; then - # Package upgrade, not uninstall - /bin/systemctl try-restart ovn-controller.service >/dev/null 2>&1 || : - fi -%endif - -%postun vtep -%if 0%{?systemd_postun_with_restart:1} - %systemd_postun_with_restart ovn-controller-vtep.service -%else - /bin/systemctl daemon-reload >/dev/null 2>&1 || : - if [ "$1" -ge "1" ] ; then - # Package upgrade, not uninstall - /bin/systemctl try-restart ovn-controller-vtep.service >/dev/null 2>&1 || : - fi -%endif - -%posttrans central -if [ $1 -eq 1 ]; then - # Package install, not upgrade - if [ -e %{_localstatedir}/lib/rpm-state/ovn-northd ]; then - rm %{_localstatedir}/lib/rpm-state/ovn-northd - /bin/systemctl start ovn-northd.service >/dev/null 2>&1 || : - fi -fi - - -%posttrans host -if [ $1 -eq 1 ]; then - # Package install, not upgrade - if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller ]; then - rm %{_localstatedir}/lib/rpm-state/ovn-controller - /bin/systemctl start ovn-controller.service >/dev/null 2>&1 || : - fi -fi - -%posttrans vtep -if [ $1 -eq 1 ]; then - # Package install, not upgrade - if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller-vtep ]; then - rm %{_localstatedir}/lib/rpm-state/ovn-controller-vtep - /bin/systemctl start ovn-controller-vtep.service >/dev/null 2>&1 || : - fi -fi - -%files -%{_bindir}/ovn-nbctl -%{_bindir}/ovn-sbctl -%{_bindir}/ovn-trace -%{_bindir}/ovn-detrace -%{_bindir}/ovn_detrace.py -%{_bindir}/ovn-appctl -%{_bindir}/ovn-ic-nbctl -%{_bindir}/ovn-ic-sbctl -%dir %{_datadir}/ovn/ -%dir %{_datadir}/ovn/scripts/ -%{_datadir}/ovn/scripts/ovn-ctl -%{_datadir}/ovn/scripts/ovn-lib -%{_datadir}/ovn/scripts/ovndb-servers.ocf -%{_mandir}/man8/ovn-ctl.8* -%{_mandir}/man8/ovn-appctl.8* -%{_mandir}/man8/ovn-nbctl.8* -%{_mandir}/man8/ovn-ic-nbctl.8* -%{_mandir}/man8/ovn-trace.8* -%{_mandir}/man1/ovn-detrace.1* -%{_mandir}/man7/ovn-architecture.7* -%{_mandir}/man8/ovn-sbctl.8* -%{_mandir}/man8/ovn-ic-sbctl.8* -%{_mandir}/man5/ovn-nb.5* -%{_mandir}/man5/ovn-ic-nb.5* -%{_mandir}/man5/ovn-sb.5* -%{_mandir}/man5/ovn-ic-sb.5* -%dir %{ovnlibdir}/ocf/resource.d/ovn/ -%{ovnlibdir}/ocf/resource.d/ovn/ovndb-servers -%config(noreplace) %verify(not md5 size mtime) %{_sysconfdir}/logrotate.d/ovn -%config(noreplace) %verify(not md5 size mtime) %{_sysconfdir}/sysconfig/ovn - -%files central -%{_bindir}/ovn-northd -%{_bindir}/ovn-ic -%{_mandir}/man8/ovn-northd.8* -%{_mandir}/man8/ovn-ic.8* -%{_datadir}/ovn/ovn-nb.ovsschema -%{_datadir}/ovn/ovn-ic-nb.ovsschema -%{_datadir}/ovn/ovn-sb.ovsschema -%{_datadir}/ovn/ovn-ic-sb.ovsschema -%{_unitdir}/ovn-northd.service -%{ovnlibdir}/firewalld/services/ovn-central-firewall-service.xml - -%files host -%{_bindir}/ovn-controller -%{_mandir}/man8/ovn-controller.8* -%{_unitdir}/ovn-controller.service -%{ovnlibdir}/firewalld/services/ovn-host-firewall-service.xml - -%files vtep -%{_bindir}/ovn-controller-vtep -%{_mandir}/man8/ovn-controller-vtep.8* -%{_unitdir}/ovn-controller-vtep.service - -%changelog -* Thu Sep 14 2023 Lorenzo Bianconi - 23.06.1-11 -- northd: check if parent_name is set for tag_request 0 -[Upstream: c869db90e2b18515caad8ad95555d989d3379e3f] - -* Thu Sep 14 2023 Ales Musil - 23.06.1-10 -- ofctrl: Prevent conjunction duplication (#2175928) -[Upstream: 4281178a8882d0194ce8edf35018227ab20fa80e] - -* Thu Sep 14 2023 Ales Musil - 23.06.1-9 -- ofctrl: Do not try to program long flows (#1955167) -[Upstream: f18bbbbc1ec0110cde8146ea4e2b34b1ec488ba7] - -* Mon Sep 11 2023 Dumitru Ceara - 23.06.1-8 -- northd: Always ct commit ECMP symmetric traffic in the original direction. -[Upstream: 6de90ba4c43e1798a63167fd7f790126cf240e9c] - -* Wed Sep 06 2023 Ihar Hrachyshka - 23.06.1-7 -- Use correct nw_ttl=255 to match against legit NAs -[Upstream: 059d1337af1be97b8f89fcf65e2ba6c9ae217d76] - -* Wed Sep 06 2023 Dumitru Ceara - 23.06.1-6 -- checkpatch: Ignore yml files when checking line lengths. -[Upstream: 18b9ca0630c537b142d6ac849a6a2092d4f5bc0f] - -* Wed Aug 30 2023 Ales Musil - 23.06.1-5 -- northd: Make sure that skip_snat=true is evaluated before force_snat (#2224260) -[Upstream: 04cf3fd8c2de752ac6ca538f6570547a69dcaac3] - -* Tue Aug 29 2023 Mark Michelson - 23.06.1-4 -- Prepare for 23.06.2. -[Upstream: c215b5237d46e7aa3b7e095f1e955db5b646e4eb] - diff --git a/SPECS/ovn23.09.spec b/SPECS/ovn23.09.spec new file mode 100644 index 0000000..6299c18 --- /dev/null +++ b/SPECS/ovn23.09.spec @@ -0,0 +1,930 @@ +# Copyright (C) 2009, 2010, 2013, 2014 Nicira Networks, Inc. +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. This file is offered as-is, +# without warranty of any kind. +# +# If tests have to be skipped while building, specify the '--without check' +# option. For example: +# rpmbuild -bb --without check rhel/openvswitch-fedora.spec + +# This defines the base package name's version. + +%define pkgver 2.13 +%define pkgname ovn23.09 + +# If libcap-ng isn't available and there is no need for running OVS +# as regular user, specify the '--without libcapng' +%bcond_without libcapng + +# Enable PIE, bz#955181 +%global _hardened_build 1 + +# RHEL-7 doesn't define _rundir macro yet +# Fedora 15 onwards uses /run as _rundir +%if 0%{!?_rundir:1} +%define _rundir /run +%endif + +# Build python2 (that provides python) and python3 subpackages on Fedora +# Build only python3 (that provides python) subpackage on RHEL8 +# Build only python subpackage on RHEL7 +%if 0%{?rhel} > 7 || 0%{?fedora} +# On RHEL8 Sphinx is included in buildroot +%global external_sphinx 1 +%else +# Don't use external sphinx (RHV doesn't have optional repositories enabled) +%global external_sphinx 0 +%endif + +# We would see rpmlinit error - E: hardcoded-library-path in '% {_prefix}/lib'. +# But there is no solution to fix this. Using {_lib} macro will solve the +# rpmlink error, but will install the files in /usr/lib64/. +# OVN pacemaker ocf script file is copied in /usr/lib/ocf/resource.d/ovn/ +# and we are not sure if pacemaker looks into this path to find the +# OVN resource agent script. +%global ovnlibdir %{_prefix}/lib + +Name: %{pkgname} +Summary: Open Virtual Network support +Group: System Environment/Daemons +URL: http://www.ovn.org/ +Version: 23.09.0 +Release: 103%{?commit0:.%{date}git%{shortcommit0}}%{?dist} +Provides: openvswitch%{pkgver}-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release} +Obsoletes: openvswitch%{pkgver}-ovn-common < 2.11.0-1 + +# Nearly all of openvswitch is ASL 2.0. The bugtool is LGPLv2+, and the +# lib/sflow*.[ch] files are SISSL +License: ASL 2.0 and LGPLv2+ and SISSL + +%define ovncommit 56d5cf6ccf4a7b034b3d0c009fbca7c509f2f42a + +# Always pull an upstream release, since this is what we rebase to. +Source: https://github.com/ovn-org/ovn/archive/%{ovncommit}.tar.gz#/ovn-%{version}.tar.gz + +%define ovscommit ec1d730163d984934c467e050ebf6d39f8c09384 +%define ovsshortcommit ec1d730 + +Source10: https://github.com/openvswitch/ovs/archive/%{ovscommit}.tar.gz#/openvswitch-%{ovsshortcommit}.tar.gz +%define ovsdir ovs-%{ovscommit} + +%define docutilsver 0.12 +%define pygmentsver 1.4 +%define sphinxver 1.1.3 +Source100: https://pypi.io/packages/source/d/docutils/docutils-%{docutilsver}.tar.gz +Source101: https://pypi.io/packages/source/P/Pygments/Pygments-%{pygmentsver}.tar.gz +Source102: https://pypi.io/packages/source/S/Sphinx/Sphinx-%{sphinxver}.tar.gz + +Source500: configlib.sh +Source501: gen_config_group.sh +Source502: set_config.sh + +# Important: source503 is used as the actual copy file +# @TODO: this causes a warning - fix it? +Source504: arm64-armv8a-linuxapp-gcc-config +Source505: ppc_64-power8-linuxapp-gcc-config +Source506: x86_64-native-linuxapp-gcc-config + +Patch: %{pkgname}.patch + +# FIXME Sphinx is used to generate some manpages, unfortunately, on RHEL, it's +# in the -optional repository and so we can't require it directly since RHV +# doesn't have the -optional repository enabled and so TPS fails +%if %{external_sphinx} +BuildRequires: python3-sphinx +%else +# Sphinx dependencies +BuildRequires: python-devel +BuildRequires: python-setuptools +#BuildRequires: python2-docutils +BuildRequires: python-jinja2 +BuildRequires: python-nose +#BuildRequires: python2-pygments +# docutils dependencies +BuildRequires: python-imaging +# pygments dependencies +BuildRequires: python-nose +%endif + +BuildRequires: gcc gcc-c++ make +BuildRequires: autoconf automake libtool +BuildRequires: systemd-units openssl openssl-devel +BuildRequires: python3-devel python3-setuptools +BuildRequires: desktop-file-utils +BuildRequires: groff-base graphviz +BuildRequires: unbound-devel + +# make check dependencies +BuildRequires: procps-ng +%if 0%{?rhel} == 8 || 0%{?fedora} +BuildRequires: python3-pyOpenSSL +%endif +BuildRequires: tcpdump + +%if %{with libcapng} +BuildRequires: libcap-ng libcap-ng-devel +%endif + +BuildRequires: python3-scapy + +Requires: hostname openssl iproute module-init-tools + +Requires(post): systemd-units +Requires(preun): systemd-units +Requires(postun): systemd-units + +# to skip running checks, pass --without check +%bcond_without check + +%description +OVN, the Open Virtual Network, is a system to support virtual network +abstraction. OVN complements the existing capabilities of OVS to add +native support for virtual network abstractions, such as virtual L2 and L3 +overlays and security groups. + +%package central +Summary: Open Virtual Network support +License: ASL 2.0 +Requires: %{pkgname} +Requires: firewalld-filesystem +Provides: openvswitch%{pkgver}-ovn-central = %{?epoch:%{epoch}:}%{version}-%{release} +Obsoletes: openvswitch%{pkgver}-ovn-central < 2.11.0-1 + +%description central +OVN DB servers and ovn-northd running on a central node. + +%package host +Summary: Open Virtual Network support +License: ASL 2.0 +Requires: %{pkgname} +Requires: firewalld-filesystem +Provides: openvswitch%{pkgver}-ovn-host = %{?epoch:%{epoch}:}%{version}-%{release} +Obsoletes: openvswitch%{pkgver}-ovn-host < 2.11.0-1 + +%description host +OVN controller running on each host. + +%package vtep +Summary: Open Virtual Network support +License: ASL 2.0 +Requires: %{pkgname} +Provides: openvswitch%{pkgver}-ovn-vtep = %{?epoch:%{epoch}:}%{version}-%{release} +Obsoletes: openvswitch%{pkgver}-ovn-vtep < 2.11.0-1 + +%description vtep +OVN vtep controller + +%prep +%autosetup -n ovn-%{ovncommit} -a 10 -p 1 + +%build +%if 0%{?commit0:1} +# fix the snapshot unreleased version to be the released one. +sed -i.old -e "s/^AC_INIT(openvswitch,.*,/AC_INIT(openvswitch, %{version},/" configure.ac +%endif +./boot.sh + +# OVN source code is now separate. +# Build openvswitch first. +# XXX Current openvswitch2.13 doesn't +# use "2.13.0" for version. It's a commit hash +pushd %{ovsdir} +./boot.sh +%configure \ +%if %{with libcapng} + --enable-libcapng \ +%else + --disable-libcapng \ +%endif + --enable-ssl \ + --with-pkidir=%{_sharedstatedir}/openvswitch/pki + +make %{?_smp_mflags} +popd + +# Build OVN. +# XXX OVS version needs to be updated when ovs2.13 is updated. +%configure \ + --with-ovs-source=$PWD/%{ovsdir} \ +%if %{with libcapng} + --enable-libcapng \ +%else + --disable-libcapng \ +%endif + --enable-ssl \ + --with-pkidir=%{_sharedstatedir}/openvswitch/pki + +make %{?_smp_mflags} + +%install +%make_install +install -p -D -m 0644 \ + rhel/usr_share_ovn_scripts_systemd_sysconfig.template \ + $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/ovn + +for service in ovn-controller ovn-controller-vtep ovn-northd; do + install -p -D -m 0644 \ + rhel/usr_lib_systemd_system_${service}.service \ + $RPM_BUILD_ROOT%{_unitdir}/${service}.service +done + +install -d -m 0755 $RPM_BUILD_ROOT/%{_sharedstatedir}/ovn + +install -d $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/ +install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml \ + $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/ovn-central-firewall-service.xml +install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml \ + $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/ovn-host-firewall-service.xml + +install -d -m 0755 $RPM_BUILD_ROOT%{ovnlibdir}/ocf/resource.d/ovn +ln -s %{_datadir}/ovn/scripts/ovndb-servers.ocf \ + $RPM_BUILD_ROOT%{ovnlibdir}/ocf/resource.d/ovn/ovndb-servers + +install -p -D -m 0644 rhel/etc_logrotate.d_ovn \ + $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.d/ovn + +# remove unneeded files. +rm -f $RPM_BUILD_ROOT%{_bindir}/ovs* +rm -f $RPM_BUILD_ROOT%{_bindir}/vtep-ctl +rm -f $RPM_BUILD_ROOT%{_sbindir}/ovs* +rm -f $RPM_BUILD_ROOT%{_mandir}/man1/ovs* +rm -f $RPM_BUILD_ROOT%{_mandir}/man5/ovs* +rm -f $RPM_BUILD_ROOT%{_mandir}/man5/vtep* +rm -f $RPM_BUILD_ROOT%{_mandir}/man7/ovs* +rm -f $RPM_BUILD_ROOT%{_mandir}/man8/ovs* +rm -f $RPM_BUILD_ROOT%{_mandir}/man8/vtep* +rm -rf $RPM_BUILD_ROOT%{_datadir}/ovn/python +rm -f $RPM_BUILD_ROOT%{_datadir}/ovn/scripts/ovs* +rm -rf $RPM_BUILD_ROOT%{_datadir}/ovn/bugtool-plugins +rm -f $RPM_BUILD_ROOT%{_libdir}/*.a +rm -f $RPM_BUILD_ROOT%{_libdir}/*.la +rm -f $RPM_BUILD_ROOT%{_libdir}/pkgconfig/*.pc +rm -f $RPM_BUILD_ROOT%{_includedir}/ovn/* +rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-appctl-bashcomp.bash +rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-vsctl-bashcomp.bash +rm -rf $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/openvswitch +rm -f $RPM_BUILD_ROOT%{_datadir}/ovn/scripts/ovn-bugtool* +rm -f $RPM_BUILD_ROOT/%{_bindir}/ovn-docker-overlay-driver \ + $RPM_BUILD_ROOT/%{_bindir}/ovn-docker-underlay-driver + +%check +%if %{with check} + touch resolv.conf + export OVS_RESOLV_CONF=$(pwd)/resolv.conf + if ! make check TESTSUITEFLAGS='%{_smp_mflags}'; then + cat tests/testsuite.log + if ! make check TESTSUITEFLAGS='--recheck'; then + cat tests/testsuite.log + # Presently a test case - "2796: ovn -- ovn-controller incremental processing" + # is failing on aarch64 arch. Let's not exit for this arch + # until we figure out why it is failing. + # Test case 93: ovn.at:12105 ovn -- ACLs on Port Groups is failing + # repeatedly on s390x. This needs to be investigated. + %ifnarch aarch64 + %ifnarch ppc64le + %ifnarch s390x + exit 1 + %endif + %endif + %endif + fi + fi +%endif + +%clean +rm -rf $RPM_BUILD_ROOT + +%pre central +if [ $1 -eq 1 ] ; then + # Package install. + /bin/systemctl status ovn-northd.service >/dev/null + ovn_status=$? + rpm -ql openvswitch-ovn-central > /dev/null + if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then + # ovn-northd service is running which means old openvswitch-ovn-central + # is already installed and it will be cleaned up. So start ovn-northd + # service when posttrans central is called. + touch %{_localstatedir}/lib/rpm-state/ovn-northd + fi +fi + +%pre host +if [ $1 -eq 1 ] ; then + # Package install. + /bin/systemctl status ovn-controller.service >/dev/null + ovn_status=$? + rpm -ql openvswitch-ovn-host > /dev/null + if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then + # ovn-controller service is running which means old + # openvswitch-ovn-host is installed and it will be cleaned up. So + # start ovn-controller service when posttrans host is called. + touch %{_localstatedir}/lib/rpm-state/ovn-controller + fi +fi + +%pre vtep +if [ $1 -eq 1 ] ; then + # Package install. + /bin/systemctl status ovn-controller-vtep.service >/dev/null + ovn_status=$? + rpm -ql openvswitch-ovn-vtep > /dev/null + if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then + # ovn-controller-vtep service is running which means old + # openvswitch-ovn-vtep is installed and it will be cleaned up. So + # start ovn-controller-vtep service when posttrans host is called. + touch %{_localstatedir}/lib/rpm-state/ovn-controller-vtep + fi +fi + +%preun central +%if 0%{?systemd_preun:1} + %systemd_preun ovn-northd.service +%else + if [ $1 -eq 0 ] ; then + # Package removal, not upgrade + /bin/systemctl --no-reload disable ovn-northd.service >/dev/null 2>&1 || : + /bin/systemctl stop ovn-northd.service >/dev/null 2>&1 || : + fi +%endif + +%preun host +%if 0%{?systemd_preun:1} + %systemd_preun ovn-controller.service +%else + if [ $1 -eq 0 ] ; then + # Package removal, not upgrade + /bin/systemctl --no-reload disable ovn-controller.service >/dev/null 2>&1 || : + /bin/systemctl stop ovn-controller.service >/dev/null 2>&1 || : + fi +%endif + +%preun vtep +%if 0%{?systemd_preun:1} + %systemd_preun ovn-controller-vtep.service +%else + if [ $1 -eq 0 ] ; then + # Package removal, not upgrade + /bin/systemctl --no-reload disable ovn-controller-vtep.service >/dev/null 2>&1 || : + /bin/systemctl stop ovn-controller-vtep.service >/dev/null 2>&1 || : + fi +%endif + +%post +%if %{with libcapng} +if [ $1 -eq 1 ]; then + sed -i 's:^#OVN_USER_ID=:OVN_USER_ID=:' %{_sysconfdir}/sysconfig/ovn + sed -i 's:\(.*su\).*:\1 openvswitch openvswitch:' %{_sysconfdir}/logrotate.d/ovn +fi +%endif + +%post central +%if 0%{?systemd_post:1} + %systemd_post ovn-northd.service +%else + # Package install, not upgrade + if [ $1 -eq 1 ]; then + /bin/systemctl daemon-reload >dev/null || : + fi +%endif + +%post host +%if 0%{?systemd_post:1} + %systemd_post ovn-controller.service +%else + # Package install, not upgrade + if [ $1 -eq 1 ]; then + /bin/systemctl daemon-reload >dev/null || : + fi +%endif + +%post vtep +%if 0%{?systemd_post:1} + %systemd_post ovn-controller-vtep.service +%else + # Package install, not upgrade + if [ $1 -eq 1 ]; then + /bin/systemctl daemon-reload >dev/null || : + fi +%endif + +%postun + +%postun central +%if 0%{?systemd_postun_with_restart:1} + %systemd_postun_with_restart ovn-northd.service +%else + /bin/systemctl daemon-reload >/dev/null 2>&1 || : + if [ "$1" -ge "1" ] ; then + # Package upgrade, not uninstall + /bin/systemctl try-restart ovn-northd.service >/dev/null 2>&1 || : + fi +%endif + +%postun host +%if 0%{?systemd_postun_with_restart:1} + %systemd_postun_with_restart ovn-controller.service +%else + /bin/systemctl daemon-reload >/dev/null 2>&1 || : + if [ "$1" -ge "1" ] ; then + # Package upgrade, not uninstall + /bin/systemctl try-restart ovn-controller.service >/dev/null 2>&1 || : + fi +%endif + +%postun vtep +%if 0%{?systemd_postun_with_restart:1} + %systemd_postun_with_restart ovn-controller-vtep.service +%else + /bin/systemctl daemon-reload >/dev/null 2>&1 || : + if [ "$1" -ge "1" ] ; then + # Package upgrade, not uninstall + /bin/systemctl try-restart ovn-controller-vtep.service >/dev/null 2>&1 || : + fi +%endif + +%posttrans central +if [ $1 -eq 1 ]; then + # Package install, not upgrade + if [ -e %{_localstatedir}/lib/rpm-state/ovn-northd ]; then + rm %{_localstatedir}/lib/rpm-state/ovn-northd + /bin/systemctl start ovn-northd.service >/dev/null 2>&1 || : + fi +fi + + +%posttrans host +if [ $1 -eq 1 ]; then + # Package install, not upgrade + if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller ]; then + rm %{_localstatedir}/lib/rpm-state/ovn-controller + /bin/systemctl start ovn-controller.service >/dev/null 2>&1 || : + fi +fi + +%posttrans vtep +if [ $1 -eq 1 ]; then + # Package install, not upgrade + if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller-vtep ]; then + rm %{_localstatedir}/lib/rpm-state/ovn-controller-vtep + /bin/systemctl start ovn-controller-vtep.service >/dev/null 2>&1 || : + fi +fi + +%files +%{_bindir}/ovn-nbctl +%{_bindir}/ovn-sbctl +%{_bindir}/ovn-trace +%{_bindir}/ovn-detrace +%{_bindir}/ovn_detrace.py +%{_bindir}/ovn-appctl +%{_bindir}/ovn-ic-nbctl +%{_bindir}/ovn-ic-sbctl +%dir %{_datadir}/ovn/ +%dir %{_datadir}/ovn/scripts/ +%{_datadir}/ovn/scripts/ovn-ctl +%{_datadir}/ovn/scripts/ovn-lib +%{_datadir}/ovn/scripts/ovndb-servers.ocf +%{_mandir}/man8/ovn-ctl.8* +%{_mandir}/man8/ovn-appctl.8* +%{_mandir}/man8/ovn-nbctl.8* +%{_mandir}/man8/ovn-ic-nbctl.8* +%{_mandir}/man8/ovn-trace.8* +%{_mandir}/man1/ovn-detrace.1* +%{_mandir}/man7/ovn-architecture.7* +%{_mandir}/man8/ovn-sbctl.8* +%{_mandir}/man8/ovn-ic-sbctl.8* +%{_mandir}/man5/ovn-nb.5* +%{_mandir}/man5/ovn-ic-nb.5* +%{_mandir}/man5/ovn-sb.5* +%{_mandir}/man5/ovn-ic-sb.5* +%dir %{ovnlibdir}/ocf/resource.d/ovn/ +%{ovnlibdir}/ocf/resource.d/ovn/ovndb-servers +%config(noreplace) %verify(not md5 size mtime) %{_sysconfdir}/logrotate.d/ovn +%config(noreplace) %verify(not md5 size mtime) %{_sysconfdir}/sysconfig/ovn + +%files central +%{_bindir}/ovn-northd +%{_bindir}/ovn-ic +%{_mandir}/man8/ovn-northd.8* +%{_mandir}/man8/ovn-ic.8* +%{_datadir}/ovn/ovn-nb.ovsschema +%{_datadir}/ovn/ovn-ic-nb.ovsschema +%{_datadir}/ovn/ovn-sb.ovsschema +%{_datadir}/ovn/ovn-ic-sb.ovsschema +%{_unitdir}/ovn-northd.service +%{ovnlibdir}/firewalld/services/ovn-central-firewall-service.xml + +%files host +%{_bindir}/ovn-controller +%{_mandir}/man8/ovn-controller.8* +%{_unitdir}/ovn-controller.service +%{ovnlibdir}/firewalld/services/ovn-host-firewall-service.xml + +%files vtep +%{_bindir}/ovn-controller-vtep +%{_mandir}/man8/ovn-controller-vtep.8* +%{_unitdir}/ovn-controller-vtep.service + +%changelog +* Tue Jan 23 2024 Xavier Simonart - 23.09.0-103 +- controller: fixed potential segfault when changing tunnel_key and deleting ls. +[Upstream: 120075357a624293d52a1905c47a1bd249d2157c] + +* Fri Jan 19 2024 Ales Musil - 23.09.0-102 +- northd: Use proper field for lookup_nd +[Upstream: 8e25c1c37aa3301f69bc89ee49ffaef5aa2f76fd] + +* Wed Jan 17 2024 Dumitru Ceara - 23.09.0-101 +- checkpatch.py: Port checkpatch related changes from the OVS repo. +[Upstream: bf334c65e1ead50013880049564d445919aee61f] + +* Fri Jan 12 2024 Dumitru Ceara - 23.09.0-100 +- actions: Make sure affinity learnt flows are auto deleted. +[Upstream: 6ce267af7124a93306d8b5bf4944379536ecd264] + +* Thu Jan 11 2024 Mark Michelson - 23.09.0-99 +- pinctrl: Directly retrieve desired port_binding MAC. +[Upstream: f85f5e3929c916985c7dfc0fe0f0433347d8bfae] + +* Tue Jan 09 2024 Lorenzo Bianconi - 23.09.0-98 +- test: add dedicated test for garp-max-timeout +[Upstream: 28fef02db946ba8113a2752e1abf61d8df5797e3] + +* Tue Jan 09 2024 Ales Musil - 23.09.0-97 +- treewide: Fix small memory leaks reported by static analysis +[Upstream: 0d5e6d65db19845aede9198d8e164d934a5f189e] + +* Tue Jan 09 2024 Ales Musil - 23.09.0-96 +- Documentation: Add note about pinning the container after release +[Upstream: 1a70f3f171c032c2329bb66f2e62d233ce19a494] + +* Tue Jan 09 2024 Ales Musil - 23.09.0-95 +- ci: Cover more container posibilities +[Upstream: 639aff0896527f9c48c56d6dfb3fdce84403b6dd] + +* Tue Jan 09 2024 Ales Musil - 23.09.0-94 +- ci: Build container image before very job +[Upstream: ca0f17758559ed836dfa0220e472ea99438cefb8] + +* Tue Jan 09 2024 Dumitru Ceara - 23.09.0-93 +- ovs: Bump submodule to include IDL "spurious delete" fix. +[Upstream: 9c97cdcd757ce356a85b3e6dde7eb19776fe4c38] + +* Wed Jan 03 2024 Jacob Tanenbaum - 23.09.0-92 +- Correct ethtype referencing incorrect values +[Upstream: e9863e57320d24f8fb0d02436834f795ba58ce48] + +* Mon Dec 18 2023 Numan Siddique - 23.09.0-91 +- Revert "ovn: add geneve PMTUD support" +[Upstream: ed4e4a94ba44f5d5be5148ee82f336cab3adc7ec] + +* Mon Dec 18 2023 Daniel Ding - 23.09.0-90 +- northd: forward arp request to lrp snat on. +[Upstream: 20ea3b63fb3a2fce2c9e273bfbdcb4d8399b8091] + +* Wed Dec 13 2023 Xavier Simonart - 23.09.0-89 +- northd: fix missing port up when deleting and adding back an lsp +[Upstream: 8cabb443ae88dded5cd1800bdcea5c5760954d25] + +* Fri Dec 08 2023 Dumitru Ceara - 23.09.0-88 +- ovn-macros: Make sure stopped daemons continue before the test ends. +[Upstream: e54ec661ef67cd93d1a72de907b37fab522bc2f9] + +* Thu Dec 07 2023 Dumitru Ceara - 23.09.0-87 +- system-test: Fix tcpdump usage in LB template tests. +[Upstream: 5141c9d4c7c861f6a65a711e59a4e64ae7d2fcdb] + +* Thu Dec 07 2023 Eelco Chaudron - 23.09.0-86 +- tests: Move SCTP test from kernel only to general OVN system tests. +[Upstream: 49d33629595a9c7fc44d7ac86926c83e475b322d] + +* Thu Dec 07 2023 Eelco Chaudron - 23.09.0-85 +- tests: Remove 'protoinfo' from the conntrack entries for SCTP tests. +[Upstream: d87ffbe44d5b5c3f143c1e38e868f9db636b4565] + +* Thu Dec 07 2023 Dumitru Ceara - 23.09.0-84 +- northd: Skip transient IDL records. +[Upstream: 44a40011f0b7f465c1eb60c9016bd56e09d7e538] + +* Thu Dec 07 2023 Ales Musil - 23.09.0-83 +- system-tests: Consolidate wait condition in CoPP test +[Upstream: ba7a45bde1de25868e0b16d8e58e6d523e2034ab] + +* Thu Dec 07 2023 Dumitru Ceara - 23.09.0-82 +- pinctrl: Fix up comments about sending RST packets for healthcheck. +[Upstream: 4ef375edc8bee094f24b9e649dc01ce3edd2034b] + +* Mon Dec 04 2023 Lorenzo Bianconi - 23.09.0-81 +- ovn: add geneve PMTUD support (#2241711) +[Upstream: e42ca82fb92cd69bbfd4da72b3c22bc57fc1ecd0] + +* Mon Dec 04 2023 Ihar Hrachyshka - 23.09.0-80 +- fmt_pkt: make sure scapy-server is started once +[Upstream: b788911812171ee5d9c51806b1e287be910164c9] + +* Mon Dec 04 2023 Ihar Hrachyshka - 23.09.0-79 +- fmt_pkt: improve scapy-server logging +[Upstream: 820e11754bff7b7029abf8bd8f166c169bdd8d04] + +* Mon Dec 04 2023 Ihar Hrachyshka - 23.09.0-78 +- fmt_pkt: use -S check to wait for scapy sock file +[Upstream: 2d710c3b1d9444a49f80db6058462e7d33253644] + +* Mon Dec 04 2023 Ihar Hrachyshka - 23.09.0-77 +- fmt_pkt: don't subshell when calling ovs-appctl +[Upstream: fc311bb6d6108d49b356d9c785f6d47e7dc8faff] + +* Mon Dec 04 2023 Xavier Simonart - 23.09.0-76 +- controller: fix group_table and meter_table allocation +[Upstream: acc63727d14ff7e9f447ed90115f74235f968499] + +* Fri Dec 01 2023 Mark Michelson - 23.09.0-75 +- Prepare for 23.09.2. +[Upstream: 8a000cc863773030828a4cda2167840f08c4a65c] + +* Fri Dec 01 2023 Mark Michelson - 23.09.0-74 +- Set release date for 23.09.1. +[Upstream: 0afd4e59e95b5f8c7b56760e91269786b0e0e52a] + +* Mon Nov 27 2023 Dumitru Ceara - 23.09.0-73 +- northd: Add missing stopwatch initialization. +[Upstream: 7fd87c5d0b1492c14d90faec4af4069496ae3609] + +* Fri Nov 24 2023 Xavier Simonart - 23.09.0-72 +- controller: avoid extra flows if localnet_learn_fdb is disabled +[Upstream: b2f839849c36c058f940c417dc29e26165a1d30e] + +* Fri Nov 24 2023 Xavier Simonart - 23.09.0-71 +- controller: FDB entries for localnet should not overwrite entries for vifs (#2242830) +[Upstream: 33b0117598b23b8c0877e482ee350283a147bb5f] + +* Mon Nov 20 2023 Ales Musil - 23.09.0-70 +- controller: Disable inactivity probe for statctrl +[Upstream: bbd07439b9a8cd6db901bffcac7ac17f58e33a07] + +* Mon Nov 20 2023 Evgenii Kovalev - 23.09.0-69 +- pinctrl: reset success and failures n_count regardless of svc state +[Upstream: 617b84d7dd2ce3501b49e988e1ba06e86889c9bd] + +* Mon Nov 20 2023 Evgenii Kovalev - 23.09.0-68 +- pinctrl: send RST instead of RST_ACK bit for lb hc +[Upstream: beb26027cf26271c7cd780869b540737c7916e99] + +* Mon Nov 20 2023 Dumitru Ceara - 23.09.0-67 +- controller: Don't artificially limit group and meter IDs to 16bit. +[Upstream: e9e716ad531e34766d2f02783ac08955096bf636] + +* Fri Nov 17 2023 Xavier Simonart - 23.09.0-66 +- tests: fixed race_condition with max_prefix +[Upstream: d257d800e41388bd2a387e0b6d5a0e41c2e8d8f1] + +* Fri Nov 17 2023 Xavier Simonart - 23.09.0-65 +- tests: have CHECK_NO_CHANGE_AFTER_RECOMPUTE potentially wait for ports up +[Upstream: dab54b81c7ee767943163f2aaaa27b2c4b367964] + +* Fri Nov 17 2023 Xavier Simonart - 23.09.0-64 +- tests: fixed "ovn-nbctl - daemon retry connection" +[Upstream: d2e0acb2a6aa510282da5e04036ec5258454c351] + +* Fri Nov 17 2023 Xavier Simonart - 23.09.0-63 +- tests: fixed system test "LR with SNAT fragmentation needed for external server". +[Upstream: 0bb6ba908421825428ec904d5316ae13090adbbf] + +* Fri Nov 17 2023 Xavier Simonart - 23.09.0-62 +- tests: fixed "interconnection - static multicast" and "- IGMP/MLD multicast" +[Upstream: 810d83e77ce3398bc94469404d85a01eb63e40bd] + +* Fri Nov 17 2023 martin.kalcok@canonical.com - 23.09.0-61 +- ovn-ctl man: Add election timer config to manpage +[Upstream: 25d4b6855f6ce3795314e9439716f775994c7f4d] + +* Fri Nov 17 2023 Xavier Simonart - 23.09.0-60 +- Fix flows not removed in ha migration +[Upstream: 5375cdd96eaf8e527e5afea402f279990398710c] + +* Fri Nov 17 2023 Xavier Simonart - 23.09.0-59 +- binding: handle pb->chassis and pb->up from if-status module +[Upstream: 619abe5c5e18f417fe20b252ca41b70e644466e0] + +* Fri Nov 17 2023 Xavier Simonart - 23.09.0-58 +- binding: slight refactor if no local binding in consider_iface_release +[Upstream: d039b4332b9ea739bdf6b2efc9f5f3e422fe9a42] + +* Fri Nov 17 2023 Xavier Simonart - 23.09.0-57 +- controller: have I+P assigning ct_zones for l3gateway ports +[Upstream: f5d01be7f1337bdc7885dd45592aa3b376467790] + +* Fri Nov 17 2023 Xavier Simonart - 23.09.0-56 +- tests: fixed another set of flaky ovn-ic tests +[Upstream: 650bffdbe0562dc364faaef51f51f99e82cccc56] + +* Fri Nov 17 2023 Xavier Simonart - 23.09.0-55 +- tests: wait for all flows to be installed before sending packets +[Upstream: b3d03b94178bee2479d6f66ffa34255a7feb79eb] + +* Fri Nov 17 2023 Xavier Simonart - 23.09.0-54 +- tests: fixed "ipsec -- basic configuration" +[Upstream: ac3ece28ca04cb74b21c80e2bd73767e29cca9a3] + +* Fri Nov 17 2023 Xavier Simonart - 23.09.0-53 +- tests: fixed "LSP incremental processing" +[Upstream: fcbc0ae1c66e31c38ad9d5e099237e7446958035] + +* Fri Nov 17 2023 Xavier Simonart - 23.09.0-52 +- tests: do not start backup-northd by default +[Upstream: 54fae8cbb5db827da95a2a52ff28f29e6c7740fe] + +* Fri Nov 17 2023 Xavier Simonart - 23.09.0-51 +- tests: fixed multiple tests not properly waiting for packets to be received +[Upstream: a1422144228bb9924dbc75782734a09c6ecfa534] + +* Fri Nov 17 2023 Dumitru Ceara - 23.09.0-50 +- ci: Pin Python, Fedora and Ubuntu runner versions. +[Upstream: 627955eb79c2cd374853319c1d271c2fd1aeac37] + +* Thu Nov 16 2023 Dumitru Ceara - 23.09.0-49 +- ovs: Bump submodule to include E721 fixes. +[Upstream: 1fa7628db4155d3a39d55fe61d8d19fa7d3030af] + +* Thu Nov 16 2023 Dumitru Ceara - 23.09.0-48 +- tests: Remove broken "feature inactivity probe" test. +[Upstream: 5044376da0a1c14d1ccc4b41dfdbae14e74746b2] + +* Wed Nov 15 2023 Ilya Maximets - 23.09.0-47 +- readthedocs: Add the configuration file. +[Upstream: 84c93511ce9a612b9a815cc1403b4841cc2e4c58] + +* Wed Nov 15 2023 Ilya Maximets - 23.09.0-46 +- Documentation: Use theme from Read The Docs. +[Upstream: 39236dc3151baa3ace58c3ecd62ba0384b4c7a05] + +* Wed Nov 15 2023 Dumitru Ceara - 23.09.0-45 +- ovs: Bump submodule to v3.2.1. +[Upstream: 74172ed481f7c239d9258845eb493f17d731df99] + +* Wed Nov 15 2023 Dumitru Ceara - 23.09.0-44 +- py-requirements: Remove hacking dependency and use recent flake8. +[Upstream: c6a631f066eea105c57c265dc68257d1b5ee18e4] + +* Fri Nov 03 2023 Xavier Simonart - 23.09.0-43 +- ovn-ic: wakeup on ovsdb transaction failures +[Upstream: be4364e62ac739744c1ef5bdd74a85fe39d6e37d] + +* Fri Nov 03 2023 Xavier Simonart - 23.09.0-42 +- ovn-ic: fix potential segmentation violation when ts is deleted +[Upstream: 5f84ff658bfd1c26bd9749c3d2e09a7e3567a8bd] + +* Fri Oct 20 2023 Ales Musil - 23.09.0-41 +- controller, northd: Wait for cleanup before replying to exit +[Upstream: aae5b2ec8ec9f4f9f7c9738d23818c2c4967627c] + +* Fri Oct 20 2023 Mark Michelson - 23.09.0-40 +- tests: Add missing check for scapy. +[Upstream: ea9310a5f1e37b373abffd85f7a8dd4fefc30c4e] + +* Wed Oct 18 2023 Ales Musil - 23.09.0-39 +- ci: Apply the ASAN workaround only for Clang <16 +[Upstream: 15bf24b889b178d4cdbb6166d3bc5434ec59f9fc] + +* Wed Oct 18 2023 Ales Musil - 23.09.0-38 +- ci: Use proper uname argument to get the HW type +[Upstream: 2efc23f3edf0293ec81a167e1c4bf99fe5601ca2] + +* Thu Oct 12 2023 Dumitru Ceara - 23.09.0-37 +- tests: Wait for new ovn-controllers to connect to Southbound. +[Upstream: 349266aac20f229b10ef0313c9f4e6b5f1af4ede] + +* Wed Oct 11 2023 Dumitru Ceara - 23.09.0-36 +- northd: Reset ls_datapath_group if not all chassis support it. +[Upstream: df7656fbf6a4ec1175b8f464a1aa6ed6e74fde29] + +* Wed Oct 11 2023 Lorenzo Bianconi - 23.09.0-35 +- northd: introduce ls_datapath_group column in lb sb db table +[Upstream: 276b9d47183ebd31c382742025e562fda8d14d11] + +* Wed Oct 11 2023 Lorenzo Bianconi - 23.09.0-34 +- northd: sync lb applied to logical routers in sb db lb table (#2193323) +[Upstream: c33398e32b2753dd6c0cecf35ba48ad8faa69bfc] + +* Tue Oct 10 2023 shylou - 23.09.0-33 +- northd: Avoid snat on reply packets for dgw +[Upstream: e8c79cecef9d6e15673be1a604baaaca083f0016] + +* Tue Oct 10 2023 Dumitru Ceara - 23.09.0-32 +- northd: Incrementally process SB.Load_balancer updates. +[Upstream: a9788ef39e003b04ec426761833d85bbec1f3b84] + +* Tue Oct 10 2023 Dumitru Ceara - 23.09.0-31 +- tests: Add missing --wait=sb to the LB I-P test. +[Upstream: cadfefdf1c6457d25b6d1f93e217493739418365] + +* Tue Oct 10 2023 Ales Musil - 23.09.0-30 +- system-tests: Make sure that IPv6 address is available right away +[Upstream: dc9eb3a1cc95accc37165902006db6eeab25fba6] + +* Tue Oct 10 2023 Ihar Hrachyshka - 23.09.0-29 +- Don't mention packet cloning when failing to find tunnel +[Upstream: 44ee1a6cb40395617f5dbab5829c9f436c16a783] + +* Tue Oct 10 2023 Ales Musil - 23.09.0-28 +- northd: Allow need frag to be SNATed +[Upstream: 94c8f952bb848806e04a857a84718d2744cfcb9f] + +* Tue Oct 10 2023 Ihar Hrachyshka - 23.09.0-27 +- docs: require ovn-set-local-ip for co-located ovn-controllers +[Upstream: 16bdac7965ae805040a107fc3cdade5bf4db63a2] + +* Tue Oct 10 2023 Ilya Maximets - 23.09.0-26 +- memory-trim: Fix timestamp overflow warning right after reboot. +[Upstream: 32ab7d94f9258ad6e938c715380a567b4a363a62] + +* Mon Oct 09 2023 Xavier Simonart - 23.09.0-25 +- Fix missing flows in ls_in_dhcp_options table +[Upstream: bb8fe6add97ab5fed5e4618b32c16e174faf44c8] + +* Mon Oct 09 2023 Xavier Simonart - 23.09.0-24 +- controller: throttle port claim attempts from if-status +[Upstream: bd32a6646d21c766497494c7a1a4add05a40cd22] + +* Fri Oct 06 2023 Ales Musil - 23.09.0-23 +- ci: Free up additional space for ovn-k jobs. +[Upstream: d30fe25c45620017ceea4f06e6e3ebd316ba734f] + +* Fri Oct 06 2023 Ales Musil - 23.09.0-22 +- ci: Handle google-cloud-sdk -> google-cloud-cli package name change. +[Upstream: 42e81bdcebc8cd744deb8034d2fb89ec3b85bf4a] + +* Fri Oct 06 2023 Dumitru Ceara - 23.09.0-21 +- ci: Free up disk space in a more robust way. +[Upstream: cf99264e252c20edf93ab5735e18aa3225c98398] + +* Fri Oct 06 2023 Dumitru Ceara - 23.09.0-20 +- ci: Update apt cache before installing gcc-multilib. +[Upstream: fd79876c2757f9074d38bd41cc36f59f3ba26138] + +* Fri Oct 06 2023 Xavier Simonart - 23.09.0-19 +- tests: fixed "send gratuitous ARP for NAT rules on HA distributed router" +[Upstream: 94b671cf89b27f54d1d03149de900994c79df415] + +* Fri Oct 06 2023 Xavier Simonart - 23.09.0-18 +- tests: move trim_zeros() to ovn-macros +[Upstream: 56b0435d8431518f4299c622a6ec9fc8770b8b0c] + +* Fri Oct 06 2023 Xavier Simonart - 23.09.0-17 +- tests: skip test "MAC binding aging" if scapy not available. +[Upstream: 148431080738bdec5e625a9ce8d470e365ee14f2] + +* Fri Oct 06 2023 Xavier Simonart - 23.09.0-16 +- tests: fixed "L2 Drop and Allow ACL w/ Stateful ACL" +[Upstream: 6f8719c60b8a578d564d3a6147f963fddeeacaa1] + +* Fri Oct 06 2023 Xavier Simonart - 23.09.0-15 +- tests: fixed multiple tests missing ovn-nbctl "wait" +[Upstream: f8cdfedacf212d9f103c2adba0c6805c01c68ff4] + +* Fri Oct 06 2023 Xavier Simonart - 23.09.0-14 +- tests: fixed "options:requested-chassis for logical port" +[Upstream: cd74dda22b255890a120988e8737c22a25c49957] + +* Fri Oct 06 2023 Xavier Simonart - 23.09.0-13 +- tests: fixed "Logical router policy packet marking" +[Upstream: e5a794dc30b087e0c78764326c86a3258f97bcc0] + +* Fri Oct 06 2023 Xavier Simonart - 23.09.0-12 +- tests: fixed multiple ovn-ic tests +[Upstream: 0575b97dc676d8c225bc8f63befec1bf1390ebe1] + +* Thu Oct 05 2023 Ales Musil - 23.09.0-11 +- pinctrl: Reply with correct destination for ICMPv6 RA packets +[Upstream: b93f36a248f7df3eb71b5141c5deadec7c18ee24] + +* Mon Oct 02 2023 Han Zhou - 23.09.0-10 +- ovn-controller: Add monitor condition for FDB. +[Upstream: c4008ae520af2561cfd68749227a8a468277e2e5] + +* Thu Sep 21 2023 Ihar Hrachyshka - 23.09.0-9 +- Rename scapy-server into scapy-server.py +[Upstream: d16ec6f9a063a0cb2d7bac56e23dd60d0c856b76] + +* Wed Sep 20 2023 Patryk Diak - 23.09.0-8 +- Add ovnkube-identity binary to the ovn-kubernetes Dockerfile +[Upstream: 35d9e42bc3e60629701743ca7e9d6890511cf0f5] + +* Tue Sep 19 2023 Ihar Hrachyshka - 23.09.0-7 +- tests: offload scapy transformations to a separate unixctl daemon +[Upstream: 4a82a49363a591d429d86d60f9120166ea04cb91] + +* Tue Sep 19 2023 Ales Musil - 23.09.0-6 +- northd: Remove hosting-chassis only if it's specified +[Upstream: 0b45a1a1cc6f081184d599ba139847ff03d90912] + +* Fri Sep 15 2023 Xavier Simonart - 23.09.0-5 +- QoS: Properly set qos when ovs db is read only (#2234349) +[Upstream: 9c56ac4b74f6b964f102b94404b350417b1cd772] + +* Fri Sep 15 2023 Mark Michelson - 23.09.0-4 +- Prepare for 23.09.1. +[Upstream: 36f37341d32589dcd8d4bfeb023046b07dea1a44] +