From b224d92a0b961fc846053f6e0114f8d8d073a621 Mon Sep 17 00:00:00 2001 From: Alfredo Moralejo Date: May 25 2021 11:43:37 +0000 Subject: Import ovn-2021-21.03.0-21 from Fast Datapath --- diff --git a/.ovn.metadata b/.ovn.metadata index 6f36ca7..2dec392 100644 --- a/.ovn.metadata +++ b/.ovn.metadata @@ -1,6 +1,5 @@ 002450621b33c5690060345b0aac25bc2426d675 SOURCES/docutils-0.12.tar.gz -9bd78cda74977132b211af95ac0f63bf05fafb91 SOURCES/openvswitch-2.14.90.tar.gz -057adab900c382cd8bd12966e8dfd7d7d1cb9b29 SOURCES/openvswitch-ac09cbf.tar.gz -f56373e54eec629b9d6e88e8b1c0c880bd498809 SOURCES/ovn-20.12.0.tar.gz +b7cb5bddcefce929e60e4533da84d13dc8ce4fd0 SOURCES/openvswitch-ac85cdb.tar.gz +35a22f67bf3675fce0ca8a39ee4aed7e0b716560 SOURCES/ovn-21.03.0.tar.gz d34f96421a86004aa5d26ecf975edefd09f948b1 SOURCES/Pygments-1.4.tar.gz 6beb30f18ffac3de7689b7fd63e9a8a7d9c8df3a SOURCES/Sphinx-1.1.3.tar.gz diff --git a/SOURCES/gen_config_group.sh b/SOURCES/gen_config_group.sh index 651a0c5..d1c06fe 100755 --- a/SOURCES/gen_config_group.sh +++ b/SOURCES/gen_config_group.sh @@ -207,10 +207,10 @@ do done popd >/dev/null -echo -n "For each arch ( " +printf "For each arch ( " for ((i=0; i < ${#OVS_DPDK_CONF_MACH_ARCH[@]}; i++)); do - echo -n "${OVS_DPDK_CONF_MACH_ARCH[i]} " + printf "${OVS_DPDK_CONF_MACH_ARCH[i]} " done echo "):" echo "1. ensure you enable the requisite hw" diff --git a/SOURCES/ovn-21.03.0.patch b/SOURCES/ovn-21.03.0.patch new file mode 100644 index 0000000..99c4e61 --- /dev/null +++ b/SOURCES/ovn-21.03.0.patch @@ -0,0 +1,4948 @@ +diff --git a/.ci/linux-prepare.sh b/.ci/linux-prepare.sh +index 0bb0ff096..83ad3958b 100755 +--- a/.ci/linux-prepare.sh ++++ b/.ci/linux-prepare.sh +@@ -12,5 +12,5 @@ set -ev + git clone git://git.kernel.org/pub/scm/devel/sparse/sparse.git + cd sparse && make -j4 HAVE_LLVM= HAVE_SQLITE= install && cd .. + +-pip install --disable-pip-version-check --user six flake8 hacking +-pip install --user --upgrade docutils ++pip3 install --disable-pip-version-check --user flake8 hacking sphinx pyOpenSSL ++pip3 install --upgrade --user docutils +diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml +index f3a53a8b6..91bd1e538 100644 +--- a/.github/workflows/test.yml ++++ b/.github/workflows/test.yml +@@ -13,7 +13,6 @@ jobs: + dependencies: | + automake libtool gcc bc libjemalloc1 libjemalloc-dev \ + libssl-dev llvm-dev libelf-dev libnuma-dev libpcap-dev \ +- python3-openssl python3-pip python3-sphinx \ + selinux-policy-dev + m32_dependecies: gcc-multilib + CC: ${{ matrix.compiler }} +@@ -88,11 +87,21 @@ jobs: + if: matrix.m32 != '' + run: sudo apt install -y ${{ env.m32_dependecies }} + ++ - name: update PATH ++ run: | ++ echo "$HOME/bin" >> $GITHUB_PATH ++ echo "$HOME/.local/bin" >> $GITHUB_PATH ++ ++ - name: set up python ++ uses: actions/setup-python@v2 ++ with: ++ python-version: '3.x' ++ + - name: prepare + run: ./.ci/linux-prepare.sh + + - name: build +- run: PATH="$PATH:$HOME/bin" ./.ci/linux-build.sh ++ run: ./.ci/linux-build.sh + + - name: copy logs on failure + if: failure() || cancelled() +@@ -145,10 +154,18 @@ jobs: + ref: 'master' + - name: install dependencies + run: brew install automake libtool ++ - name: update PATH ++ run: | ++ echo "$HOME/bin" >> $GITHUB_PATH ++ echo "$HOME/.local/bin" >> $GITHUB_PATH ++ - name: set up python ++ uses: actions/setup-python@v2 ++ with: ++ python-version: '3.x' + - name: prepare + run: ./.ci/osx-prepare.sh + - name: build +- run: PATH="$PATH:$HOME/bin" ./.ci/osx-build.sh ++ run: ./.ci/osx-build.sh + - name: upload logs on failure + if: failure() + uses: actions/upload-artifact@v2 +diff --git a/Makefile.am b/Makefile.am +index 80247b62d..1fe730dc4 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -221,6 +221,7 @@ dist-hook-git: distfiles + grep -v '\.gitattributes$$' | \ + grep -v '\.gitmodules$$' | \ + grep -v "$(submodules)" | \ ++ grep -v 'redhat' | \ + LC_ALL=C sort -u > all-gitfiles; \ + LC_ALL=C comm -1 -3 distfiles all-gitfiles > missing-distfiles; \ + if test -s missing-distfiles; then \ +@@ -332,7 +333,7 @@ check-tabs: + @cd $(srcdir); \ + if test -e .git && (git --version) >/dev/null 2>&1 && \ + grep -ln "^ " \ +- `git ls-files | grep -v $(submodules) \ ++ `git ls-files | grep -v $(submodules) | grep -v redhat \ + | grep -v -f build-aux/initial-tab-whitelist` /dev/null \ + | $(EGREP) -v ':[ ]*/?\*'; \ + then \ +diff --git a/NEWS b/NEWS +index 5372668bf..530c5d42f 100644 +--- a/NEWS ++++ b/NEWS +@@ -1,3 +1,13 @@ ++Post-v21.03.0 ++------------------------- ++ - ovn-northd-ddlog: New implementation of northd, based on DDlog. This ++ implementation is incremental, meaning that it only recalculates what is ++ needed for the southbound database when northbound changes occur. It is ++ expected to scale better than the C implementation, for large deployments. ++ (This may take testing and tuning to be effective.) This version of OVN ++ requires DDLog 0.36. ++ - Introduce ovn-controller incremetal processing engine statistics ++ + OVN v21.03.0 - 12 Mar 2021 + ------------------------- + - Support ECMP multiple nexthops for reroute router policies. +diff --git a/configure.ac b/configure.ac +index 37b476d53..f3de6fef2 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -13,7 +13,7 @@ + # limitations under the License. + + AC_PREREQ(2.63) +-AC_INIT(ovn, 21.03.0, bugs@openvswitch.org) ++AC_INIT(ovn, 21.03.1, bugs@openvswitch.org) + AC_CONFIG_MACRO_DIR([m4]) + AC_CONFIG_AUX_DIR([build-aux]) + AC_CONFIG_HEADERS([config.h]) +diff --git a/controller/binding.c b/controller/binding.c +index 4e6c75696..514f5f33f 100644 +--- a/controller/binding.c ++++ b/controller/binding.c +@@ -597,6 +597,23 @@ remove_local_lport_ids(const struct sbrec_port_binding *pb, + } + } + ++/* Corresponds to each Port_Binding.type. */ ++enum en_lport_type { ++ LP_UNKNOWN, ++ LP_VIF, ++ LP_CONTAINER, ++ LP_PATCH, ++ LP_L3GATEWAY, ++ LP_LOCALNET, ++ LP_LOCALPORT, ++ LP_L2GATEWAY, ++ LP_VTEP, ++ LP_CHASSISREDIRECT, ++ LP_VIRTUAL, ++ LP_EXTERNAL, ++ LP_REMOTE ++}; ++ + /* Local bindings. binding.c module binds the logical port (represented by + * Port_Binding rows) and sets the 'chassis' column when it sees the + * OVS interface row (of type "" or "internal") with the +@@ -608,134 +625,180 @@ remove_local_lport_ids(const struct sbrec_port_binding *pb, + * 'struct local_binding' is used. A shash of these local bindings is + * maintained with the 'external_ids:iface-id' as the key to the shash. + * +- * struct local_binding (defined in binding.h) has 3 main fields: +- * - type +- * - OVS interface row object +- * - Port_Binding row object +- * +- * An instance of 'struct local_binding' can be one of 3 types. +- * +- * BT_VIF: Represent a local binding for an OVS interface of +- * type "" or "internal" with the external_ids:iface-id +- * set. +- * +- * This can be a +- * * probable local binding - external_ids:iface-id is +- * set, but the corresponding Port_Binding row is not +- * created or is not visible to the local ovn-controller +- * instance. +- * +- * * a local binding - external_ids:iface-id is set and +- * which is already bound to the corresponding Port_Binding +- * row. +- * +- * It maintains a list of children +- * (of type BT_CONTAINER/BT_VIRTUAL) if any. +- * +- * BT_CONTAINER: Represents a local binding which has a parent of type +- * BT_VIF. Its Port_Binding row's 'parent' column is set to +- * its parent's Port_Binding. It shares the OVS interface row +- * with the parent. +- * Each ovn-controller when it sees a container Port_Binding, +- * it creates 'struct local_binding' for the parent +- * Port_Binding and for its even if the OVS interface row for +- * the parent is not present. +- * +- * BT_VIRTUAL: Represents a local binding which has a parent of type BT_VIF. +- * Its Port_Binding type is "virtual" and it shares the OVS +- * interface row with the parent. +- * Port_Binding of type "virtual" is claimed by pinctrl module +- * when it sees the ARP packet from the parent's VIF. +- * ++ * struct local_binding has 3 main fields: ++ * - name : 'external_ids:iface-id' of the OVS interface (key). ++ * - OVS interface row object. ++ * - List of 'binding_lport' objects with the primary lport ++ * in the front of the list (if present). + * + * An object of 'struct local_binding' is created: +- * - For each interface that has iface-id configured with the type - BT_VIF. +- * +- * - For each container Port Binding (of type BT_CONTAINER) and its +- * parent Port_Binding (of type BT_VIF), no matter if +- * they are bound to this chassis i.e even if OVS interface row for the +- * parent is not present. ++ * - For each interface that has external_ids:iface-id configured. + * +- * - For each 'virtual' Port Binding (of type BT_VIRTUAL) provided its parent +- * is bound to this chassis. ++ * - For each port binding (also referred as lport) of type 'LP_VIF' ++ * if it is a parent lport of container lports even if there is no ++ * corresponding OVS interface. + */ ++struct local_binding { ++ char *name; ++ const struct ovsrec_interface *iface; ++ struct ovs_list binding_lports; ++}; + +-static struct local_binding * +-local_binding_create(const char *name, const struct ovsrec_interface *iface, +- const struct sbrec_port_binding *pb, +- enum local_binding_type type) +-{ +- struct local_binding *lbinding = xzalloc(sizeof *lbinding); +- lbinding->name = xstrdup(name); +- lbinding->type = type; +- lbinding->pb = pb; +- lbinding->iface = iface; +- shash_init(&lbinding->children); +- return lbinding; +-} +- +-static void +-local_binding_add(struct shash *local_bindings, struct local_binding *lbinding) +-{ +- shash_add(local_bindings, lbinding->name, lbinding); +-} ++/* This structure represents a logical port (or port binding) ++ * which is associated with 'struct local_binding'. ++ * ++ * An instance of 'struct binding_lport' is created for a logical port ++ * - If the OVS interface's iface-id corresponds to the logical port. ++ * - If it is a container or virtual logical port and its parent ++ * has a 'local binding'. ++ * ++ */ ++struct binding_lport { ++ struct ovs_list list_node; /* Node in local_binding.binding_lports. */ + +-static void +-local_binding_destroy(struct local_binding *lbinding) +-{ +- local_bindings_destroy(&lbinding->children); ++ char *name; ++ const struct sbrec_port_binding *pb; ++ struct local_binding *lbinding; ++ enum en_lport_type type; ++}; + +- free(lbinding->name); +- free(lbinding); +-} ++static struct local_binding *local_binding_create( ++ const char *name, const struct ovsrec_interface *); ++static void local_binding_add(struct shash *local_bindings, ++ struct local_binding *); ++static struct local_binding *local_binding_find( ++ struct shash *local_bindings, const char *name); ++static void local_binding_destroy(struct local_binding *, ++ struct shash *binding_lports); ++static void local_binding_delete(struct local_binding *, ++ struct shash *local_bindings, ++ struct shash *binding_lports); ++static struct binding_lport *local_binding_add_lport( ++ struct shash *binding_lports, ++ struct local_binding *, ++ const struct sbrec_port_binding *, ++ enum en_lport_type); ++static struct binding_lport *local_binding_get_primary_lport( ++ struct local_binding *); ++static bool local_binding_handle_stale_binding_lports( ++ struct local_binding *lbinding, struct binding_ctx_in *b_ctx_in, ++ struct binding_ctx_out *b_ctx_out, struct hmap *qos_map); ++ ++static struct binding_lport *binding_lport_create( ++ const struct sbrec_port_binding *, ++ struct local_binding *, enum en_lport_type); ++static void binding_lport_destroy(struct binding_lport *); ++static void binding_lport_delete(struct shash *binding_lports, ++ struct binding_lport *); ++static void binding_lport_add(struct shash *binding_lports, ++ struct binding_lport *); ++static struct binding_lport *binding_lport_find( ++ struct shash *binding_lports, const char *lport_name); ++static const struct sbrec_port_binding *binding_lport_get_parent_pb( ++ struct binding_lport *b_lprt); ++static struct binding_lport *binding_lport_check_and_cleanup( ++ struct binding_lport *, struct shash *b_lports); ++ ++static char *get_lport_type_str(enum en_lport_type lport_type); + + void +-local_bindings_init(struct shash *local_bindings) ++local_binding_data_init(struct local_binding_data *lbinding_data) + { +- shash_init(local_bindings); ++ shash_init(&lbinding_data->bindings); ++ shash_init(&lbinding_data->lports); + } + + void +-local_bindings_destroy(struct shash *local_bindings) ++local_binding_data_destroy(struct local_binding_data *lbinding_data) + { + struct shash_node *node, *next; +- SHASH_FOR_EACH_SAFE (node, next, local_bindings) { ++ ++ SHASH_FOR_EACH_SAFE (node, next, &lbinding_data->lports) { ++ struct binding_lport *b_lport = node->data; ++ binding_lport_destroy(b_lport); ++ shash_delete(&lbinding_data->lports, node); ++ } ++ ++ SHASH_FOR_EACH_SAFE (node, next, &lbinding_data->bindings) { + struct local_binding *lbinding = node->data; +- local_binding_destroy(lbinding); +- shash_delete(local_bindings, node); ++ local_binding_destroy(lbinding, &lbinding_data->lports); ++ shash_delete(&lbinding_data->bindings, node); + } + +- shash_destroy(local_bindings); ++ shash_destroy(&lbinding_data->lports); ++ shash_destroy(&lbinding_data->bindings); + } + +-static +-void local_binding_delete(struct shash *local_bindings, +- struct local_binding *lbinding) ++const struct sbrec_port_binding * ++local_binding_get_primary_pb(struct shash *local_bindings, const char *pb_name) + { +- shash_find_and_delete(local_bindings, lbinding->name); +- local_binding_destroy(lbinding); +-} ++ struct local_binding *lbinding = ++ local_binding_find(local_bindings, pb_name); ++ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding); + +-static void +-local_binding_add_child(struct local_binding *lbinding, +- struct local_binding *child) +-{ +- local_binding_add(&lbinding->children, child); +- child->parent = lbinding; ++ return b_lport ? b_lport->pb : NULL; + } + +-static struct local_binding * +-local_binding_find_child(struct local_binding *lbinding, +- const char *child_name) ++void ++binding_dump_local_bindings(struct local_binding_data *lbinding_data, ++ struct ds *out_data) + { +- return local_binding_find(&lbinding->children, child_name); +-} ++ const struct shash_node **nodes; + +-static void +-local_binding_delete_child(struct local_binding *lbinding, +- struct local_binding *child) +-{ +- shash_find_and_delete(&lbinding->children, child->name); ++ nodes = shash_sort(&lbinding_data->bindings); ++ size_t n = shash_count(&lbinding_data->bindings); ++ ++ ds_put_cstr(out_data, "Local bindings:\n"); ++ for (size_t i = 0; i < n; i++) { ++ const struct shash_node *node = nodes[i]; ++ struct local_binding *lbinding = node->data; ++ size_t num_lports = ovs_list_size(&lbinding->binding_lports); ++ ds_put_format(out_data, "name: [%s], OVS interface name : [%s], " ++ "num binding lports : [%"PRIuSIZE"]\n", ++ lbinding->name, ++ lbinding->iface ? lbinding->iface->name : "NULL", ++ num_lports); ++ ++ if (num_lports) { ++ struct shash child_lports = SHASH_INITIALIZER(&child_lports); ++ struct binding_lport *primary_lport = NULL; ++ struct binding_lport *b_lport; ++ bool first_elem = true; ++ ++ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { ++ if (first_elem && b_lport->type == LP_VIF) { ++ primary_lport = b_lport; ++ } else { ++ shash_add(&child_lports, b_lport->name, b_lport); ++ } ++ first_elem = false; ++ } ++ ++ if (primary_lport) { ++ ds_put_format(out_data, "primary lport : [%s]\n", ++ primary_lport->name); ++ } else { ++ ds_put_format(out_data, "no primary lport\n"); ++ } ++ ++ if (!shash_is_empty(&child_lports)) { ++ const struct shash_node **c_nodes = ++ shash_sort(&child_lports); ++ for (size_t j = 0; j < shash_count(&child_lports); j++) { ++ b_lport = c_nodes[j]->data; ++ ds_put_format(out_data, "child lport[%"PRIuSIZE"] : [%s], " ++ "type : [%s]\n", j + 1, b_lport->name, ++ get_lport_type_str(b_lport->type)); ++ } ++ free(c_nodes); ++ } ++ shash_destroy(&child_lports); ++ } ++ ++ ds_put_cstr(out_data, "----------------------------------------\n"); ++ } ++ ++ free(nodes); + } + + static bool +@@ -744,12 +807,6 @@ is_lport_vif(const struct sbrec_port_binding *pb) + return !pb->type[0]; + } + +-static bool +-is_lport_container(const struct sbrec_port_binding *pb) +-{ +- return is_lport_vif(pb) && pb->parent_port && pb->parent_port[0]; +-} +- + static struct tracked_binding_datapath * + tracked_binding_datapath_create(const struct sbrec_datapath_binding *dp, + bool is_new, +@@ -818,26 +875,13 @@ binding_tracked_dp_destroy(struct hmap *tracked_datapaths) + hmap_destroy(tracked_datapaths); + } + +-/* Corresponds to each Port_Binding.type. */ +-enum en_lport_type { +- LP_UNKNOWN, +- LP_VIF, +- LP_PATCH, +- LP_L3GATEWAY, +- LP_LOCALNET, +- LP_LOCALPORT, +- LP_L2GATEWAY, +- LP_VTEP, +- LP_CHASSISREDIRECT, +- LP_VIRTUAL, +- LP_EXTERNAL, +- LP_REMOTE +-}; +- + static enum en_lport_type + get_lport_type(const struct sbrec_port_binding *pb) + { + if (is_lport_vif(pb)) { ++ if (pb->parent_port && pb->parent_port[0]) { ++ return LP_CONTAINER; ++ } + return LP_VIF; + } else if (!strcmp(pb->type, "patch")) { + return LP_PATCH; +@@ -864,6 +908,41 @@ get_lport_type(const struct sbrec_port_binding *pb) + return LP_UNKNOWN; + } + ++static char * ++get_lport_type_str(enum en_lport_type lport_type) ++{ ++ switch (lport_type) { ++ case LP_VIF: ++ return "VIF"; ++ case LP_CONTAINER: ++ return "CONTAINER"; ++ case LP_VIRTUAL: ++ return "VIRTUAL"; ++ case LP_PATCH: ++ return "PATCH"; ++ case LP_CHASSISREDIRECT: ++ return "CHASSISREDIRECT"; ++ case LP_L3GATEWAY: ++ return "L3GATEWAT"; ++ case LP_LOCALNET: ++ return "PATCH"; ++ case LP_LOCALPORT: ++ return "LOCALPORT"; ++ case LP_L2GATEWAY: ++ return "L2GATEWAY"; ++ case LP_EXTERNAL: ++ return "EXTERNAL"; ++ case LP_REMOTE: ++ return "REMOTE"; ++ case LP_VTEP: ++ return "VTEP"; ++ case LP_UNKNOWN: ++ return "UNKNOWN"; ++ } ++ ++ OVS_NOT_REACHED(); ++} ++ + /* For newly claimed ports, if 'notify_up' is 'false': + * - set the 'pb.up' field to true if 'pb' has no 'parent_pb'. + * - set the 'pb.up' field to true if 'parent_pb.up' is 'true' (e.g., for +@@ -991,14 +1070,15 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly, + static bool + is_lbinding_set(struct local_binding *lbinding) + { +- return lbinding && lbinding->pb && lbinding->iface; ++ return lbinding && lbinding->iface; + } + + static bool +-is_lbinding_this_chassis(struct local_binding *lbinding, +- const struct sbrec_chassis *chassis) ++is_binding_lport_this_chassis(struct binding_lport *b_lport, ++ const struct sbrec_chassis *chassis) + { +- return lbinding && lbinding->pb && lbinding->pb->chassis == chassis; ++ return (b_lport && b_lport->pb && chassis && ++ b_lport->pb->chassis == chassis); + } + + static bool +@@ -1010,15 +1090,14 @@ can_bind_on_this_chassis(const struct sbrec_chassis *chassis_rec, + || !strcmp(requested_chassis, chassis_rec->hostname); + } + +-/* Returns 'true' if the 'lbinding' has children of type BT_CONTAINER, ++/* Returns 'true' if the 'lbinding' has binding lports of type LP_CONTAINER, + * 'false' otherwise. */ + static bool + is_lbinding_container_parent(struct local_binding *lbinding) + { +- struct shash_node *node; +- SHASH_FOR_EACH (node, &lbinding->children) { +- struct local_binding *l = node->data; +- if (l->type == BT_CONTAINER) { ++ struct binding_lport *b_lport; ++ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { ++ if (b_lport->type == LP_CONTAINER) { + return true; + } + } +@@ -1027,66 +1106,41 @@ is_lbinding_container_parent(struct local_binding *lbinding) + } + + static bool +-release_local_binding_children(const struct sbrec_chassis *chassis_rec, +- struct local_binding *lbinding, +- bool sb_readonly, +- struct hmap *tracked_dp_bindings) +-{ +- struct shash_node *node; +- SHASH_FOR_EACH (node, &lbinding->children) { +- struct local_binding *l = node->data; +- if (is_lbinding_this_chassis(l, chassis_rec)) { +- if (!release_lport(l->pb, sb_readonly, tracked_dp_bindings)) { +- return false; +- } ++release_binding_lport(const struct sbrec_chassis *chassis_rec, ++ struct binding_lport *b_lport, bool sb_readonly, ++ struct binding_ctx_out *b_ctx_out) ++{ ++ if (is_binding_lport_this_chassis(b_lport, chassis_rec)) { ++ remove_local_lport_ids(b_lport->pb, b_ctx_out); ++ if (!release_lport(b_lport->pb, sb_readonly, ++ b_ctx_out->tracked_dp_bindings)) { ++ return false; + } +- +- /* Clear the local bindings' 'iface'. */ +- l->iface = NULL; + } + + return true; + } + +-static bool +-release_local_binding(const struct sbrec_chassis *chassis_rec, +- struct local_binding *lbinding, bool sb_readonly, +- struct hmap *tracked_dp_bindings) +-{ +- if (!release_local_binding_children(chassis_rec, lbinding, +- sb_readonly, tracked_dp_bindings)) { +- return false; +- } +- +- bool retval = true; +- if (is_lbinding_this_chassis(lbinding, chassis_rec)) { +- retval = release_lport(lbinding->pb, sb_readonly, tracked_dp_bindings); +- } +- +- lbinding->pb = NULL; +- lbinding->iface = NULL; +- return retval; +-} +- + static bool + consider_vif_lport_(const struct sbrec_port_binding *pb, + bool can_bind, const char *vif_chassis, + struct binding_ctx_in *b_ctx_in, + struct binding_ctx_out *b_ctx_out, +- struct local_binding *lbinding, ++ struct binding_lport *b_lport, + struct hmap *qos_map) + { +- bool lbinding_set = is_lbinding_set(lbinding); ++ bool lbinding_set = b_lport && is_lbinding_set(b_lport->lbinding); ++ + if (lbinding_set) { + if (can_bind) { + /* We can claim the lport. */ + const struct sbrec_port_binding *parent_pb = +- lbinding->parent ? lbinding->parent->pb : NULL; ++ binding_lport_get_parent_pb(b_lport); + + if (!claim_lport(pb, parent_pb, b_ctx_in->chassis_rec, +- lbinding->iface, !b_ctx_in->ovnsb_idl_txn, +- !lbinding->parent, +- b_ctx_out->tracked_dp_bindings)){ ++ b_lport->lbinding->iface, ++ !b_ctx_in->ovnsb_idl_txn, ++ !parent_pb, b_ctx_out->tracked_dp_bindings)){ + return false; + } + +@@ -1098,7 +1152,7 @@ consider_vif_lport_(const struct sbrec_port_binding *pb, + b_ctx_out->tracked_dp_bindings); + update_local_lport_ids(pb, b_ctx_out); + update_local_lports(pb->logical_port, b_ctx_out); +- if (lbinding->iface && qos_map && b_ctx_in->ovs_idl_txn) { ++ if (b_lport->lbinding->iface && qos_map && b_ctx_in->ovs_idl_txn) { + get_qos_params(pb, qos_map); + } + } else { +@@ -1136,16 +1190,19 @@ consider_vif_lport(const struct sbrec_port_binding *pb, + vif_chassis); + + if (!lbinding) { +- lbinding = local_binding_find(b_ctx_out->local_bindings, ++ lbinding = local_binding_find(&b_ctx_out->lbinding_data->bindings, + pb->logical_port); + } + ++ struct binding_lport *b_lport = NULL; + if (lbinding) { +- lbinding->pb = pb; ++ struct shash *binding_lports = ++ &b_ctx_out->lbinding_data->lports; ++ b_lport = local_binding_add_lport(binding_lports, lbinding, pb, LP_VIF); + } + + return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in, +- b_ctx_out, lbinding, qos_map); ++ b_ctx_out, b_lport, qos_map); + } + + static bool +@@ -1154,9 +1211,9 @@ consider_container_lport(const struct sbrec_port_binding *pb, + struct binding_ctx_out *b_ctx_out, + struct hmap *qos_map) + { ++ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; + struct local_binding *parent_lbinding; +- parent_lbinding = local_binding_find(b_ctx_out->local_bindings, +- pb->parent_port); ++ parent_lbinding = local_binding_find(local_bindings, pb->parent_port); + + if (!parent_lbinding) { + /* There is no local_binding for parent port. Create it +@@ -1171,54 +1228,61 @@ consider_container_lport(const struct sbrec_port_binding *pb, + * we want the these container ports also be claimed by the + * chassis. + * */ +- parent_lbinding = local_binding_create(pb->parent_port, NULL, NULL, +- BT_VIF); +- local_binding_add(b_ctx_out->local_bindings, parent_lbinding); ++ parent_lbinding = local_binding_create(pb->parent_port, NULL); ++ local_binding_add(local_bindings, parent_lbinding); + } + +- struct local_binding *container_lbinding = +- local_binding_find_child(parent_lbinding, pb->logical_port); ++ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports; ++ struct binding_lport *container_b_lport = ++ local_binding_add_lport(binding_lports, parent_lbinding, pb, ++ LP_CONTAINER); + +- if (!container_lbinding) { +- container_lbinding = local_binding_create(pb->logical_port, +- parent_lbinding->iface, +- pb, BT_CONTAINER); +- local_binding_add_child(parent_lbinding, container_lbinding); +- } else { +- ovs_assert(container_lbinding->type == BT_CONTAINER); +- container_lbinding->pb = pb; +- container_lbinding->iface = parent_lbinding->iface; +- } ++ struct binding_lport *parent_b_lport = ++ binding_lport_find(binding_lports, pb->parent_port); + +- if (!parent_lbinding->pb) { +- parent_lbinding->pb = lport_lookup_by_name( ++ bool can_consider_c_lport = true; ++ if (!parent_b_lport || !parent_b_lport->pb) { ++ const struct sbrec_port_binding *parent_pb = lport_lookup_by_name( + b_ctx_in->sbrec_port_binding_by_name, pb->parent_port); + +- if (parent_lbinding->pb) { ++ if (parent_pb && get_lport_type(parent_pb) == LP_VIF) { + /* Its possible that the parent lport is not considered yet. + * So call consider_vif_lport() to process it first. */ +- consider_vif_lport(parent_lbinding->pb, b_ctx_in, b_ctx_out, ++ consider_vif_lport(parent_pb, b_ctx_in, b_ctx_out, + parent_lbinding, qos_map); ++ parent_b_lport = binding_lport_find(binding_lports, ++ pb->parent_port); + } else { +- /* The parent lport doesn't exist. Call release_lport() to +- * release the container lport, if it was bound earlier. */ +- if (is_lbinding_this_chassis(container_lbinding, +- b_ctx_in->chassis_rec)) { +- return release_lport(pb, !b_ctx_in->ovnsb_idl_txn, +- b_ctx_out->tracked_dp_bindings); +- } ++ /* The parent lport doesn't exist. Cannot consider the container ++ * lport for binding. */ ++ can_consider_c_lport = false; ++ } ++ } + +- return true; ++ if (parent_b_lport && parent_b_lport->type != LP_VIF) { ++ can_consider_c_lport = false; ++ } ++ ++ if (!can_consider_c_lport) { ++ /* Call release_lport() to release the container lport, ++ * if it was bound earlier. */ ++ if (is_binding_lport_this_chassis(container_b_lport, ++ b_ctx_in->chassis_rec)) { ++ return release_lport(pb, !b_ctx_in->ovnsb_idl_txn, ++ b_ctx_out->tracked_dp_bindings); + } ++ ++ return true; + } + +- const char *vif_chassis = smap_get(&parent_lbinding->pb->options, ++ ovs_assert(parent_b_lport && parent_b_lport->pb); ++ const char *vif_chassis = smap_get(&parent_b_lport->pb->options, + "requested-chassis"); + bool can_bind = can_bind_on_this_chassis(b_ctx_in->chassis_rec, + vif_chassis); + + return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in, b_ctx_out, +- container_lbinding, qos_map); ++ container_b_lport, qos_map); + } + + static bool +@@ -1227,46 +1291,58 @@ consider_virtual_lport(const struct sbrec_port_binding *pb, + struct binding_ctx_out *b_ctx_out, + struct hmap *qos_map) + { +- struct local_binding * parent_lbinding = +- pb->virtual_parent ? local_binding_find(b_ctx_out->local_bindings, ++ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; ++ struct local_binding *parent_lbinding = ++ pb->virtual_parent ? local_binding_find(local_bindings, + pb->virtual_parent) + : NULL; + +- if (parent_lbinding && !parent_lbinding->pb) { +- parent_lbinding->pb = lport_lookup_by_name( +- b_ctx_in->sbrec_port_binding_by_name, pb->virtual_parent); +- +- if (parent_lbinding->pb) { +- /* Its possible that the parent lport is not considered yet. +- * So call consider_vif_lport() to process it first. */ +- consider_vif_lport(parent_lbinding->pb, b_ctx_in, b_ctx_out, +- parent_lbinding, qos_map); +- } +- } +- ++ struct binding_lport *virtual_b_lport = NULL; + /* Unlike container lports, we don't have to create parent_lbinding if + * it is NULL. This is because, if parent_lbinding is not present, it + * means the virtual port can't bind in this chassis. + * Note: pinctrl module binds the virtual lport when it sees ARP + * packet from the parent lport. */ +- struct local_binding *virtual_lbinding = NULL; +- if (is_lbinding_this_chassis(parent_lbinding, b_ctx_in->chassis_rec)) { +- virtual_lbinding = +- local_binding_find_child(parent_lbinding, pb->logical_port); +- if (!virtual_lbinding) { +- virtual_lbinding = local_binding_create(pb->logical_port, +- parent_lbinding->iface, +- pb, BT_VIRTUAL); +- local_binding_add_child(parent_lbinding, virtual_lbinding); +- } else { +- ovs_assert(virtual_lbinding->type == BT_VIRTUAL); +- virtual_lbinding->pb = pb; +- virtual_lbinding->iface = parent_lbinding->iface; ++ if (parent_lbinding) { ++ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports; ++ ++ struct binding_lport *parent_b_lport = ++ binding_lport_find(binding_lports, pb->virtual_parent); ++ ++ if (!parent_b_lport || !parent_b_lport->pb) { ++ const struct sbrec_port_binding *parent_pb = lport_lookup_by_name( ++ b_ctx_in->sbrec_port_binding_by_name, pb->virtual_parent); ++ ++ if (parent_pb && get_lport_type(parent_pb) == LP_VIF) { ++ /* Its possible that the parent lport is not considered yet. ++ * So call consider_vif_lport() to process it first. */ ++ consider_vif_lport(parent_pb, b_ctx_in, b_ctx_out, ++ parent_lbinding, qos_map); ++ } ++ } ++ ++ parent_b_lport = local_binding_get_primary_lport(parent_lbinding); ++ if (is_binding_lport_this_chassis(parent_b_lport, ++ b_ctx_in->chassis_rec)) { ++ virtual_b_lport = ++ local_binding_add_lport(binding_lports, parent_lbinding, pb, ++ LP_VIRTUAL); + } + } + +- return consider_vif_lport_(pb, true, NULL, b_ctx_in, b_ctx_out, +- virtual_lbinding, qos_map); ++ if (!consider_vif_lport_(pb, true, NULL, b_ctx_in, b_ctx_out, ++ virtual_b_lport, qos_map)) { ++ return false; ++ } ++ ++ /* If the virtual lport is not bound to this chassis, then remove ++ * its entry from the local_lport_ids if present. This is required ++ * when a virtual port moves from one chassis to other.*/ ++ if (!virtual_b_lport) { ++ remove_local_lport_ids(pb, b_ctx_out); ++ } ++ ++ return true; + } + + /* Considers either claiming the lport or releasing the lport +@@ -1407,6 +1483,8 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in, + continue; + } + ++ struct shash *local_bindings = ++ &b_ctx_out->lbinding_data->bindings; + for (j = 0; j < port_rec->n_interfaces; j++) { + const struct ovsrec_interface *iface_rec; + +@@ -1416,11 +1494,10 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in, + + if (iface_id && ofport > 0) { + struct local_binding *lbinding = +- local_binding_find(b_ctx_out->local_bindings, iface_id); ++ local_binding_find(local_bindings, iface_id); + if (!lbinding) { +- lbinding = local_binding_create(iface_id, iface_rec, NULL, +- BT_VIF); +- local_binding_add(b_ctx_out->local_bindings, lbinding); ++ lbinding = local_binding_create(iface_id, iface_rec); ++ local_binding_add(local_bindings, lbinding); + } else { + static struct vlog_rate_limit rl = + VLOG_RATE_LIMIT_INIT(1, 5); +@@ -1431,7 +1508,6 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in, + "configuration on interface [%s]", + lbinding->iface->name, iface_rec->name, + iface_rec->name); +- ovs_assert(lbinding->type == BT_VIF); + } + + update_local_lports(iface_id, b_ctx_out); +@@ -1494,11 +1570,11 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) + break; + + case LP_VIF: +- if (is_lport_container(pb)) { +- consider_container_lport(pb, b_ctx_in, b_ctx_out, qos_map_ptr); +- } else { +- consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL, qos_map_ptr); +- } ++ consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL, qos_map_ptr); ++ break; ++ ++ case LP_CONTAINER: ++ consider_container_lport(pb, b_ctx_in, b_ctx_out, qos_map_ptr); + break; + + case LP_VIRTUAL: +@@ -1799,39 +1875,44 @@ consider_iface_claim(const struct ovsrec_interface *iface_rec, + update_local_lports(iface_id, b_ctx_out); + smap_replace(b_ctx_out->local_iface_ids, iface_rec->name, iface_id); + +- struct local_binding *lbinding = +- local_binding_find(b_ctx_out->local_bindings, iface_id); ++ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; ++ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports; ++ struct local_binding *lbinding = local_binding_find(local_bindings, ++ iface_id); + + if (!lbinding) { +- lbinding = local_binding_create(iface_id, iface_rec, NULL, BT_VIF); +- local_binding_add(b_ctx_out->local_bindings, lbinding); ++ lbinding = local_binding_create(iface_id, iface_rec); ++ local_binding_add(local_bindings, lbinding); + } else { + lbinding->iface = iface_rec; + } + +- if (!lbinding->pb || strcmp(lbinding->name, lbinding->pb->logical_port)) { +- lbinding->pb = lport_lookup_by_name( +- b_ctx_in->sbrec_port_binding_by_name, lbinding->name); +- if (lbinding->pb && !strcmp(lbinding->pb->type, "virtual")) { +- lbinding->pb = NULL; ++ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding); ++ const struct sbrec_port_binding *pb = NULL; ++ if (!b_lport) { ++ pb = lport_lookup_by_name(b_ctx_in->sbrec_port_binding_by_name, ++ lbinding->name); ++ if (pb && get_lport_type(pb) == LP_VIF) { ++ b_lport = local_binding_add_lport(binding_lports, lbinding, pb, ++ LP_VIF); + } + } + +- if (lbinding->pb) { +- if (!consider_vif_lport(lbinding->pb, b_ctx_in, b_ctx_out, +- lbinding, qos_map)) { +- return false; +- } ++ if (!b_lport) { ++ /* There is no binding lport for this local binding. */ ++ return true; ++ } ++ ++ if (!consider_vif_lport(b_lport->pb, b_ctx_in, b_ctx_out, ++ lbinding, qos_map)) { ++ return false; + } + + /* Update the child local_binding's iface (if any children) and try to + * claim the container lbindings. */ +- struct shash_node *node; +- SHASH_FOR_EACH (node, &lbinding->children) { +- struct local_binding *child = node->data; +- child->iface = iface_rec; +- if (child->type == BT_CONTAINER) { +- if (!consider_container_lport(child->pb, b_ctx_in, b_ctx_out, ++ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { ++ if (b_lport->type == LP_CONTAINER) { ++ if (!consider_container_lport(b_lport->pb, b_ctx_in, b_ctx_out, + qos_map)) { + return false; + } +@@ -1862,32 +1943,42 @@ consider_iface_release(const struct ovsrec_interface *iface_rec, + struct binding_ctx_out *b_ctx_out) + { + struct local_binding *lbinding; +- lbinding = local_binding_find(b_ctx_out->local_bindings, +- iface_id); +- if (is_lbinding_this_chassis(lbinding, b_ctx_in->chassis_rec)) { ++ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; ++ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports; ++ ++ lbinding = local_binding_find(local_bindings, iface_id); ++ struct binding_lport *b_lport = local_binding_get_primary_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, +- lbinding->pb->datapath->tunnel_key); ++ b_lport->pb->datapath->tunnel_key); + if (ld) { +- remove_pb_from_local_datapath(lbinding->pb, +- b_ctx_in->chassis_rec, +- b_ctx_out, ld); ++ remove_pb_from_local_datapath(b_lport->pb, ++ b_ctx_in->chassis_rec, ++ b_ctx_out, ld); + } + +- /* Note: release_local_binding() resets lbinding->pb and +- * lbinding->iface. +- * Cannot access these members of lbinding after this call. */ +- if (!release_local_binding(b_ctx_in->chassis_rec, lbinding, +- !b_ctx_in->ovnsb_idl_txn, +- b_ctx_out->tracked_dp_bindings)) { +- return false; ++ /* 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) { ++ /* Clear the iface of the local binding. */ ++ lbinding->iface = NULL; + } + + /* 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(b_ctx_out->local_bindings, lbinding); ++ local_binding_delete(lbinding, local_bindings, binding_lports); + } + + remove_local_lports(iface_id, b_ctx_out); +@@ -2088,56 +2179,35 @@ handle_deleted_lport(const struct sbrec_port_binding *pb, + } + } + +-static struct local_binding * +-get_lbinding_for_lport(const struct sbrec_port_binding *pb, +- enum en_lport_type lport_type, +- struct binding_ctx_out *b_ctx_out) +-{ +- ovs_assert(lport_type == LP_VIF || lport_type == LP_VIRTUAL); +- +- if (lport_type == LP_VIF && !is_lport_container(pb)) { +- return local_binding_find(b_ctx_out->local_bindings, pb->logical_port); +- } +- +- struct local_binding *parent_lbinding = NULL; +- +- if (lport_type == LP_VIRTUAL) { +- if (pb->virtual_parent) { +- parent_lbinding = local_binding_find(b_ctx_out->local_bindings, +- pb->virtual_parent); +- } +- } else { +- if (pb->parent_port) { +- parent_lbinding = local_binding_find(b_ctx_out->local_bindings, +- pb->parent_port); +- } +- } +- +- return parent_lbinding +- ? local_binding_find(&parent_lbinding->children, pb->logical_port) +- : NULL; +-} +- + static bool + handle_deleted_vif_lport(const struct sbrec_port_binding *pb, + enum en_lport_type lport_type, + struct binding_ctx_in *b_ctx_in, + struct binding_ctx_out *b_ctx_out) + { +- struct local_binding *lbinding = +- get_lbinding_for_lport(pb, lport_type, b_ctx_out); ++ struct local_binding *lbinding = NULL; ++ bool bound = false; + +- if (lbinding) { +- lbinding->pb = NULL; +- /* The port_binding 'pb' is deleted. So there is no need to +- * clear the 'chassis' column of 'pb'. But we need to do +- * for the local_binding's children. */ +- if (lbinding->type == BT_VIF && +- !release_local_binding_children( +- b_ctx_in->chassis_rec, lbinding, +- !b_ctx_in->ovnsb_idl_txn, +- b_ctx_out->tracked_dp_bindings)) { +- return false; ++ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports; ++ struct binding_lport *b_lport = binding_lport_find(binding_lports, pb->logical_port); ++ if (b_lport) { ++ lbinding = b_lport->lbinding; ++ bound = is_binding_lport_this_chassis(b_lport, b_ctx_in->chassis_rec); ++ ++ /* Remove b_lport from local_binding. */ ++ binding_lport_delete(binding_lports, b_lport); ++ } ++ ++ if (bound && lbinding && lport_type == LP_VIF) { ++ /* We need to release the container/virtual binding lports (if any) if ++ * deleted 'pb' type is LP_VIF. */ ++ struct binding_lport *c_lport; ++ LIST_FOR_EACH (c_lport, list_node, &lbinding->binding_lports) { ++ if (!release_binding_lport(b_ctx_in->chassis_rec, c_lport, ++ !b_ctx_in->ovnsb_idl_txn, ++ b_ctx_out)) { ++ return false; ++ } + } + } + +@@ -2147,18 +2217,8 @@ handle_deleted_vif_lport(const struct sbrec_port_binding *pb, + * it from local_lports if there is a VIF entry. + * consider_iface_release() takes care of removing from the local_lports + * when the interface change happens. */ +- if (is_lport_container(pb)) { ++ if (lport_type == LP_CONTAINER) { + remove_local_lports(pb->logical_port, b_ctx_out); +- +- /* If the container port is removed we should also remove it from +- * its parent's children set. +- */ +- if (lbinding) { +- if (lbinding->parent) { +- local_binding_delete_child(lbinding->parent, lbinding); +- } +- local_binding_destroy(lbinding); +- } + } + + handle_deleted_lport(pb, b_ctx_in, b_ctx_out); +@@ -2177,7 +2237,7 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, + + if (lport_type == LP_VIRTUAL) { + handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out, qos_map); +- } else if (lport_type == LP_VIF && is_lport_container(pb)) { ++ } else if (lport_type == LP_CONTAINER) { + handled = consider_container_lport(pb, b_ctx_in, b_ctx_out, qos_map); + } else { + handled = consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL, qos_map); +@@ -2189,14 +2249,14 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, + + bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec); + +- if (lport_type == LP_VIRTUAL || +- (lport_type == LP_VIF && is_lport_container(pb)) || ++ if (lport_type == LP_VIRTUAL || lport_type == LP_CONTAINER || + claimed == now_claimed) { + return true; + } + +- struct local_binding *lbinding = +- local_binding_find(b_ctx_out->local_bindings, pb->logical_port); ++ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; ++ struct local_binding *lbinding = local_binding_find(local_bindings, ++ pb->logical_port); + + /* If the ovs port backing this binding previously was removed in the + * meantime, we won't have a local_binding for it. +@@ -2206,12 +2266,11 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, + return true; + } + +- struct shash_node *node; +- SHASH_FOR_EACH (node, &lbinding->children) { +- struct local_binding *child = node->data; +- if (child->type == BT_CONTAINER) { +- handled = consider_container_lport(child->pb, b_ctx_in, b_ctx_out, +- qos_map); ++ struct binding_lport *b_lport; ++ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { ++ if (b_lport->type == LP_CONTAINER) { ++ handled = consider_container_lport(b_lport->pb, b_ctx_in, ++ b_ctx_out, qos_map); + if (!handled) { + return false; + } +@@ -2256,12 +2315,25 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, + + enum en_lport_type lport_type = get_lport_type(pb); + +- if (lport_type == LP_VIF) { +- if (is_lport_container(pb)) { +- shash_add(&deleted_container_pbs, pb->logical_port, pb); +- } else { +- shash_add(&deleted_vif_pbs, pb->logical_port, pb); ++ struct binding_lport *b_lport = ++ binding_lport_find(&b_ctx_out->lbinding_data->lports, ++ pb->logical_port); ++ if (b_lport) { ++ /* If the 'b_lport->type' and 'lport_type' don't match, then update ++ * the b_lport->type to the updated 'lport_type'. The function ++ * binding_lport_check_and_cleanup() will cleanup the 'b_lport' ++ * if required. */ ++ if (b_lport->type != lport_type) { ++ b_lport->type = lport_type; + } ++ b_lport = binding_lport_check_and_cleanup( ++ b_lport, &b_ctx_out->lbinding_data->lports); ++ } ++ ++ if (lport_type == LP_VIF) { ++ shash_add(&deleted_vif_pbs, pb->logical_port, pb); ++ } else if (lport_type == LP_CONTAINER) { ++ shash_add(&deleted_container_pbs, pb->logical_port, pb); + } else if (lport_type == LP_VIRTUAL) { + shash_add(&deleted_virtual_pbs, pb->logical_port, pb); + } else { +@@ -2272,7 +2344,7 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, + struct shash_node *node; + struct shash_node *node_next; + SHASH_FOR_EACH_SAFE (node, node_next, &deleted_container_pbs) { +- handled = handle_deleted_vif_lport(node->data, LP_VIF, b_ctx_in, ++ handled = handle_deleted_vif_lport(node->data, LP_CONTAINER, b_ctx_in, + b_ctx_out); + shash_delete(&deleted_container_pbs, node); + if (!handled) { +@@ -2326,12 +2398,33 @@ delete_done: + + enum en_lport_type lport_type = get_lport_type(pb); + ++ struct binding_lport *b_lport = ++ binding_lport_find(&b_ctx_out->lbinding_data->lports, ++ pb->logical_port); ++ if (b_lport) { ++ ovs_assert(b_lport->pb == pb); ++ ++ if (b_lport->type != lport_type) { ++ b_lport->type = lport_type; ++ } ++ ++ if (b_lport->lbinding) { ++ handled = local_binding_handle_stale_binding_lports( ++ b_lport->lbinding, b_ctx_in, b_ctx_out, qos_map_ptr); ++ if (!handled) { ++ /* Backout from the handling. */ ++ break; ++ } ++ } ++ } ++ + struct local_datapath *ld = + get_local_datapath(b_ctx_out->local_datapaths, + pb->datapath->tunnel_key); + + switch (lport_type) { + case LP_VIF: ++ case LP_CONTAINER: + case LP_VIRTUAL: + handled = handle_updated_vif_lport(pb, lport_type, b_ctx_in, + b_ctx_out, qos_map_ptr); +@@ -2468,11 +2561,11 @@ binding_init(void) + * available. + */ + void +-binding_seqno_run(struct shash *local_bindings) ++binding_seqno_run(struct local_binding_data *lbinding_data) + { + const char *iface_id; + const char *iface_id_next; +- ++ struct shash *local_bindings = &lbinding_data->bindings; + SSET_FOR_EACH_SAFE (iface_id, iface_id_next, &binding_iface_released_set) { + struct shash_node *lb_node = shash_find(local_bindings, iface_id); + +@@ -2508,16 +2601,17 @@ binding_seqno_run(struct shash *local_bindings) + * If so, then this is a newly bound interface, make sure we reset the + * Port_Binding 'up' field and the OVS Interface 'external-id'. + */ +- if (lb && lb->pb && lb->iface) { ++ struct binding_lport *b_lport = local_binding_get_primary_lport(lb); ++ if (lb && b_lport && lb->iface) { + new_ifaces = true; + + if (smap_get(&lb->iface->external_ids, OVN_INSTALLED_EXT_ID)) { + ovsrec_interface_update_external_ids_delkey( + lb->iface, OVN_INSTALLED_EXT_ID); + } +- if (lb->pb->n_up) { ++ if (b_lport->pb->n_up) { + bool up = false; +- sbrec_port_binding_set_up(lb->pb, &up, 1); ++ sbrec_port_binding_set_up(b_lport->pb, &up, 1); + } + simap_put(&binding_iface_seqno_map, lb->name, new_seqno); + } +@@ -2542,12 +2636,13 @@ binding_seqno_run(struct shash *local_bindings) + * available. + */ + void +-binding_seqno_install(struct shash *local_bindings) ++binding_seqno_install(struct local_binding_data *lbinding_data) + { + struct ofctrl_acked_seqnos *acked_seqnos = + ofctrl_acked_seqnos_get(binding_seq_type_pb_cfg); + struct simap_node *node; + struct simap_node *node_next; ++ struct shash *local_bindings = &lbinding_data->bindings; + + SIMAP_FOR_EACH_SAFE (node, node_next, &binding_iface_seqno_map) { + struct shash_node *lb_node = shash_find(local_bindings, node->name); +@@ -2557,7 +2652,8 @@ binding_seqno_install(struct shash *local_bindings) + } + + struct local_binding *lb = lb_node->data; +- if (!lb->pb || !lb->iface) { ++ struct binding_lport *b_lport = local_binding_get_primary_lport(lb); ++ if (!b_lport || !lb->iface) { + goto del_seqno; + } + +@@ -2568,14 +2664,12 @@ binding_seqno_install(struct shash *local_bindings) + ovsrec_interface_update_external_ids_setkey(lb->iface, + OVN_INSTALLED_EXT_ID, + "true"); +- if (lb->pb->n_up) { ++ if (b_lport->pb->n_up) { + bool up = true; + +- sbrec_port_binding_set_up(lb->pb, &up, 1); +- struct shash_node *child_node; +- SHASH_FOR_EACH (child_node, &lb->children) { +- struct local_binding *lb_child = child_node->data; +- sbrec_port_binding_set_up(lb_child->pb, &up, 1); ++ sbrec_port_binding_set_up(b_lport->pb, &up, 1); ++ LIST_FOR_EACH (b_lport, list_node, &lb->binding_lports) { ++ sbrec_port_binding_set_up(b_lport->pb, &up, 1); + } + } + +@@ -2591,3 +2685,305 @@ binding_seqno_flush(void) + { + simap_clear(&binding_iface_seqno_map); + } ++ ++/* Static functions for local_lbindind and binding_lport. */ ++static struct local_binding * ++local_binding_create(const char *name, const struct ovsrec_interface *iface) ++{ ++ struct local_binding *lbinding = xzalloc(sizeof *lbinding); ++ lbinding->name = xstrdup(name); ++ lbinding->iface = iface; ++ ovs_list_init(&lbinding->binding_lports); ++ ++ return lbinding; ++} ++ ++static struct local_binding * ++local_binding_find(struct shash *local_bindings, const char *name) ++{ ++ return shash_find_data(local_bindings, name); ++} ++ ++static void ++local_binding_add(struct shash *local_bindings, struct local_binding *lbinding) ++{ ++ shash_add(local_bindings, lbinding->name, lbinding); ++} ++ ++static void ++local_binding_destroy(struct local_binding *lbinding, ++ struct shash *binding_lports) ++{ ++ struct binding_lport *b_lport; ++ LIST_FOR_EACH_POP (b_lport, list_node, &lbinding->binding_lports) { ++ b_lport->lbinding = NULL; ++ binding_lport_delete(binding_lports, b_lport); ++ } ++ ++ free(lbinding->name); ++ free(lbinding); ++} ++ ++static void ++local_binding_delete(struct local_binding *lbinding, ++ struct shash *local_bindings, ++ struct shash *binding_lports) ++{ ++ shash_find_and_delete(local_bindings, lbinding->name); ++ local_binding_destroy(lbinding, binding_lports); ++} ++ ++/* Returns the primary binding lport if present in lbinding's ++ * binding lports list. A binding lport is considered primary ++ * if binding lport's type is LP_VIF and the name matches ++ * with the 'lbinding'. ++ */ ++static struct binding_lport * ++local_binding_get_primary_lport(struct local_binding *lbinding) ++{ ++ if (!lbinding) { ++ return NULL; ++ } ++ ++ if (!ovs_list_is_empty(&lbinding->binding_lports)) { ++ struct binding_lport *b_lport = NULL; ++ b_lport = CONTAINER_OF(ovs_list_front(&lbinding->binding_lports), ++ struct binding_lport, list_node); ++ ++ if (b_lport->type == LP_VIF && ++ !strcmp(lbinding->name, b_lport->name)) { ++ return b_lport; ++ } ++ } ++ ++ return NULL; ++} ++ ++static struct binding_lport * ++local_binding_add_lport(struct shash *binding_lports, ++ struct local_binding *lbinding, ++ const struct sbrec_port_binding *pb, ++ enum en_lport_type b_type) ++{ ++ struct binding_lport *b_lport = ++ binding_lport_find(binding_lports, pb->logical_port); ++ bool add_to_lport_list = false; ++ if (!b_lport) { ++ b_lport = binding_lport_create(pb, lbinding, b_type); ++ binding_lport_add(binding_lports, b_lport); ++ add_to_lport_list = true; ++ } else if (b_lport->lbinding != lbinding) { ++ add_to_lport_list = true; ++ if (!ovs_list_is_empty(&b_lport->list_node)) { ++ ovs_list_remove(&b_lport->list_node); ++ } ++ b_lport->lbinding = lbinding; ++ b_lport->type = b_type; ++ } ++ ++ if (add_to_lport_list) { ++ if (b_type == LP_VIF) { ++ ovs_list_push_front(&lbinding->binding_lports, &b_lport->list_node); ++ } else { ++ ovs_list_push_back(&lbinding->binding_lports, &b_lport->list_node); ++ } ++ } ++ ++ return b_lport; ++} ++ ++/* This function handles the stale binding lports of 'lbinding' if 'lbinding' ++ * doesn't have a primary binding lport. ++ */ ++static bool ++local_binding_handle_stale_binding_lports(struct local_binding *lbinding, ++ struct binding_ctx_in *b_ctx_in, ++ struct binding_ctx_out *b_ctx_out, ++ struct hmap *qos_map) ++{ ++ /* Check if this lbinding has a primary binding_lport or not. */ ++ struct binding_lport *p_lport = local_binding_get_primary_lport(lbinding); ++ if (p_lport) { ++ /* Nothing to be done. */ ++ return true; ++ } ++ ++ bool handled = true; ++ struct binding_lport *b_lport, *next; ++ const struct sbrec_port_binding *pb; ++ LIST_FOR_EACH_SAFE (b_lport, next, list_node, &lbinding->binding_lports) { ++ /* Get the lport type again from the pb. Its possible that the ++ * pb type has changed. */ ++ enum en_lport_type pb_lport_type = get_lport_type(b_lport->pb); ++ if (b_lport->type == LP_VIRTUAL && pb_lport_type == LP_VIRTUAL) { ++ pb = b_lport->pb; ++ binding_lport_delete(&b_ctx_out->lbinding_data->lports, ++ b_lport); ++ handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out, qos_map); ++ } else if (b_lport->type == LP_CONTAINER && ++ pb_lport_type == LP_CONTAINER) { ++ /* For container lport, binding_lport is preserved so that when ++ * the parent port is created, it can be considered. ++ * consider_container_lport() creates the binding_lport for the parent ++ * port (with iface set to NULL). */ ++ handled = consider_container_lport(b_lport->pb, b_ctx_in, b_ctx_out, qos_map); ++ } else { ++ /* This can happen when the lport type changes from one type ++ * to another. Eg. from normal lport to external. Release the ++ * lport if it was claimed earlier and delete the b_lport. */ ++ handled = release_binding_lport(b_ctx_in->chassis_rec, b_lport, ++ !b_ctx_in->ovnsb_idl_txn, ++ b_ctx_out); ++ binding_lport_delete(&b_ctx_out->lbinding_data->lports, ++ b_lport); ++ } ++ ++ if (!handled) { ++ return false; ++ } ++ } ++ ++ return handled; ++} ++ ++static struct binding_lport * ++binding_lport_create(const struct sbrec_port_binding *pb, ++ struct local_binding *lbinding, ++ enum en_lport_type type) ++{ ++ struct binding_lport *b_lport = xzalloc(sizeof *b_lport); ++ b_lport->name = xstrdup(pb->logical_port); ++ b_lport->pb = pb; ++ b_lport->type = type; ++ b_lport->lbinding = lbinding; ++ ovs_list_init(&b_lport->list_node); ++ ++ return b_lport; ++} ++ ++static void ++binding_lport_add(struct shash *binding_lports, struct binding_lport *b_lport) ++{ ++ shash_add(binding_lports, b_lport->pb->logical_port, b_lport); ++} ++ ++static struct binding_lport * ++binding_lport_find(struct shash *binding_lports, const char *lport_name) ++{ ++ if (!lport_name) { ++ return NULL; ++ } ++ ++ return shash_find_data(binding_lports, lport_name); ++} ++ ++static void ++binding_lport_destroy(struct binding_lport *b_lport) ++{ ++ if (!ovs_list_is_empty(&b_lport->list_node)) { ++ ovs_list_remove(&b_lport->list_node); ++ } ++ ++ free(b_lport->name); ++ free(b_lport); ++} ++ ++static void ++binding_lport_delete(struct shash *binding_lports, ++ struct binding_lport *b_lport) ++{ ++ shash_find_and_delete(binding_lports, b_lport->name); ++ binding_lport_destroy(b_lport); ++} ++ ++ ++static const struct sbrec_port_binding * ++binding_lport_get_parent_pb(struct binding_lport *b_lport) ++{ ++ if (!b_lport) { ++ return NULL; ++ } ++ ++ if (b_lport->type == LP_VIF) { ++ return NULL; ++ } ++ ++ struct local_binding *lbinding = b_lport->lbinding; ++ ovs_assert(lbinding); ++ ++ struct binding_lport *parent_b_lport = ++ local_binding_get_primary_lport(lbinding); ++ ++ return parent_b_lport ? parent_b_lport->pb : NULL; ++} ++ ++/* This function checks and cleans up the 'b_lport' if it is ++ * not in the correct state. ++ * ++ * If the 'b_lport' type is LP_VIF, then its name and its lbinding->name ++ * should match. Otherwise this should be cleaned up. ++ * ++ * If the 'b_lport' type is LP_CONTAINER, then its parent_port name should ++ * be the same as its lbinding's name. Otherwise this should be ++ * cleaned up. ++ * ++ * If the 'b_lport' type is LP_VIRTUAL, then its virtual parent name ++ * should be the same as its lbinding's name. Otherwise this ++ * should be cleaned up. ++ * ++ * If the 'b_lport' type is not LP_VIF, LP_CONTAINER or LP_VIRTUAL, it ++ * should be cleaned up. This can happen if the CMS changes ++ * the port binding type. ++ */ ++static struct binding_lport * ++binding_lport_check_and_cleanup(struct binding_lport *b_lport, ++ struct shash *binding_lports) ++{ ++ bool cleanup_blport = false; ++ ++ if (!b_lport->lbinding) { ++ cleanup_blport = true; ++ goto cleanup; ++ } ++ ++ switch (b_lport->type) { ++ case LP_VIF: ++ if (strcmp(b_lport->name, b_lport->lbinding->name)) { ++ cleanup_blport = true; ++ } ++ break; ++ ++ case LP_CONTAINER: ++ if (strcmp(b_lport->pb->parent_port, b_lport->lbinding->name)) { ++ cleanup_blport = true; ++ } ++ break; ++ ++ case LP_VIRTUAL: ++ if (!b_lport->pb->virtual_parent || ++ strcmp(b_lport->pb->virtual_parent, b_lport->lbinding->name)) { ++ cleanup_blport = true; ++ } ++ break; ++ ++ case LP_PATCH: ++ case LP_LOCALPORT: ++ case LP_VTEP: ++ case LP_L2GATEWAY: ++ case LP_L3GATEWAY: ++ case LP_CHASSISREDIRECT: ++ case LP_EXTERNAL: ++ case LP_LOCALNET: ++ case LP_REMOTE: ++ case LP_UNKNOWN: ++ cleanup_blport = true; ++ } ++ ++cleanup: ++ if (cleanup_blport) { ++ binding_lport_delete(binding_lports, b_lport); ++ return NULL; ++ } ++ ++ return b_lport; ++} +diff --git a/controller/binding.h b/controller/binding.h +index c9ebef4b1..4fc9ef207 100644 +--- a/controller/binding.h ++++ b/controller/binding.h +@@ -36,6 +36,7 @@ struct sbrec_chassis; + struct sbrec_port_binding_table; + struct sset; + struct sbrec_port_binding; ++struct ds; + + struct binding_ctx_in { + struct ovsdb_idl_txn *ovnsb_idl_txn; +@@ -56,7 +57,7 @@ struct binding_ctx_in { + + struct binding_ctx_out { + struct hmap *local_datapaths; +- struct shash *local_bindings; ++ struct local_binding_data *lbinding_data; + + /* sset of (potential) local lports. */ + struct sset *local_lports; +@@ -86,28 +87,16 @@ struct binding_ctx_out { + struct hmap *tracked_dp_bindings; + }; + +-enum local_binding_type { +- BT_VIF, +- BT_CONTAINER, +- BT_VIRTUAL ++struct local_binding_data { ++ struct shash bindings; ++ struct shash lports; + }; + +-struct local_binding { +- char *name; +- enum local_binding_type type; +- const struct ovsrec_interface *iface; +- const struct sbrec_port_binding *pb; +- +- /* shash of 'struct local_binding' representing children. */ +- struct shash children; +- struct local_binding *parent; +-}; ++void local_binding_data_init(struct local_binding_data *); ++void local_binding_data_destroy(struct local_binding_data *); + +-static inline struct local_binding * +-local_binding_find(struct shash *local_bindings, const char *name) +-{ +- return shash_find_data(local_bindings, name); +-} ++const struct sbrec_port_binding *local_binding_get_primary_pb( ++ struct shash *local_bindings, const char *pb_name); + + /* Represents a tracked binding logical port. */ + struct tracked_binding_lport { +@@ -128,8 +117,6 @@ bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn, + const struct sbrec_port_binding_table *, + const struct sbrec_chassis *); + +-void local_bindings_init(struct shash *local_bindings); +-void local_bindings_destroy(struct shash *local_bindings); + bool binding_handle_ovs_interface_changes(struct binding_ctx_in *, + struct binding_ctx_out *); + bool binding_handle_port_binding_changes(struct binding_ctx_in *, +@@ -137,7 +124,8 @@ bool binding_handle_port_binding_changes(struct binding_ctx_in *, + void binding_tracked_dp_destroy(struct hmap *tracked_datapaths); + + void binding_init(void); +-void binding_seqno_run(struct shash *local_bindings); +-void binding_seqno_install(struct shash *local_bindings); ++void binding_seqno_run(struct local_binding_data *lbinding_data); ++void binding_seqno_install(struct local_binding_data *lbinding_data); + void binding_seqno_flush(void); ++void binding_dump_local_bindings(struct local_binding_data *, struct ds *); + #endif /* controller/binding.h */ +diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml +index 51c0c372c..8886df568 100644 +--- a/controller/ovn-controller.8.xml ++++ b/controller/ovn-controller.8.xml +@@ -578,6 +578,28 @@ + Displays logical flow cache statistics: enabled/disabled, per cache + type entry counts. + ++ ++
inc-engine/show-stats
++
++ Display ovn-controller engine counters. For each engine ++ node the following counters have been added: ++ ++
++ ++
inc-engine/clear-stats
++
++ Reset ovn-controller engine counters. ++
+ +

+ +diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c +index 5dd643f52..9102b9903 100644 +--- a/controller/ovn-controller.c ++++ b/controller/ovn-controller.c +@@ -81,6 +81,7 @@ static unixctl_cb_func cluster_state_reset_cmd; + static unixctl_cb_func debug_pause_execution; + static unixctl_cb_func debug_resume_execution; + static unixctl_cb_func debug_status_execution; ++static unixctl_cb_func debug_dump_local_bindings; + static unixctl_cb_func lflow_cache_flush_cmd; + static unixctl_cb_func lflow_cache_show_stats_cmd; + static unixctl_cb_func debug_delay_nb_cfg_report; +@@ -1182,8 +1183,7 @@ struct ed_type_runtime_data { + /* Contains "struct local_datapath" nodes. */ + struct hmap local_datapaths; + +- /* Contains "struct local_binding" nodes. */ +- struct shash local_bindings; ++ struct local_binding_data lbinding_data; + + /* Contains the name of each logical port resident on the local + * hypervisor. These logical ports include the VIFs (and their child +@@ -1222,9 +1222,9 @@ struct ed_type_runtime_data { + * | | Interface and Port Binding changes store the | + * | @tracked_dp_bindings | changed datapaths (datapaths added/removed from | + * | | local_datapaths) and changed port bindings | +- * | | (added/updated/deleted in 'local_bindings'). | ++ * | | (added/updated/deleted in 'lbinding_data'). | + * | | So any changes to the runtime data - | +- * | | local_datapaths and local_bindings is captured | ++ * | | local_datapaths and lbinding_data is captured | + * | | here. | + * ------------------------------------------------------------------------ + * | | This is a bool which represents if the runtime | +@@ -1251,7 +1251,7 @@ struct ed_type_runtime_data { + * + * --------------------------------------------------------------------- + * | local_datapaths | The changes to these runtime data is captured in | +- * | local_bindings | the @tracked_dp_bindings indirectly and hence it | ++ * | lbinding_data | the @tracked_dp_bindings indirectly and hence it | + * | local_lport_ids | is not tracked explicitly. | + * --------------------------------------------------------------------- + * | local_iface_ids | This is used internally within the runtime data | +@@ -1294,7 +1294,7 @@ en_runtime_data_init(struct engine_node *node OVS_UNUSED, + sset_init(&data->active_tunnels); + sset_init(&data->egress_ifaces); + smap_init(&data->local_iface_ids); +- local_bindings_init(&data->local_bindings); ++ local_binding_data_init(&data->lbinding_data); + + /* Init the tracked data. */ + hmap_init(&data->tracked_dp_bindings); +@@ -1322,7 +1322,7 @@ en_runtime_data_cleanup(void *data) + free(cur_node); + } + hmap_destroy(&rt_data->local_datapaths); +- local_bindings_destroy(&rt_data->local_bindings); ++ local_binding_data_destroy(&rt_data->lbinding_data); + hmapx_destroy(&rt_data->ct_updated_datapaths); + } + +@@ -1405,7 +1405,7 @@ init_binding_ctx(struct engine_node *node, + b_ctx_out->local_lport_ids_changed = false; + b_ctx_out->non_vif_ports_changed = false; + b_ctx_out->egress_ifaces = &rt_data->egress_ifaces; +- b_ctx_out->local_bindings = &rt_data->local_bindings; ++ b_ctx_out->lbinding_data = &rt_data->lbinding_data; + b_ctx_out->local_iface_ids = &rt_data->local_iface_ids; + b_ctx_out->tracked_dp_bindings = NULL; + b_ctx_out->local_lports_changed = NULL; +@@ -1449,7 +1449,7 @@ en_runtime_data_run(struct engine_node *node, void *data) + free(cur_node); + } + hmap_clear(local_datapaths); +- local_bindings_destroy(&rt_data->local_bindings); ++ local_binding_data_destroy(&rt_data->lbinding_data); + sset_destroy(local_lports); + sset_destroy(local_lport_ids); + sset_destroy(active_tunnels); +@@ -1460,7 +1460,7 @@ en_runtime_data_run(struct engine_node *node, void *data) + sset_init(active_tunnels); + sset_init(&rt_data->egress_ifaces); + smap_init(&rt_data->local_iface_ids); +- local_bindings_init(&rt_data->local_bindings); ++ local_binding_data_init(&rt_data->lbinding_data); + hmapx_clear(&rt_data->ct_updated_datapaths); + } + +@@ -1822,7 +1822,7 @@ static void init_physical_ctx(struct engine_node *node, + p_ctx->local_lports = &rt_data->local_lports; + p_ctx->ct_zones = ct_zones; + p_ctx->mff_ovn_geneve = ed_mff_ovn_geneve->mff_ovn_geneve; +- p_ctx->local_bindings = &rt_data->local_bindings; ++ p_ctx->local_bindings = &rt_data->lbinding_data.bindings; + p_ctx->ct_updated_datapaths = &rt_data->ct_updated_datapaths; + } + +@@ -2685,7 +2685,8 @@ main(int argc, char *argv[]) + engine_get_internal_data(&en_flow_output); + struct ed_type_ct_zones *ct_zones_data = + engine_get_internal_data(&en_ct_zones); +- struct ed_type_runtime_data *runtime_data = NULL; ++ struct ed_type_runtime_data *runtime_data = ++ engine_get_internal_data(&en_runtime_data); + + ofctrl_init(&flow_output_data->group_table, + &flow_output_data->meter_table, +@@ -2738,6 +2739,10 @@ main(int argc, char *argv[]) + unixctl_command_register("debug/delay-nb-cfg-report", "SECONDS", 1, 1, + debug_delay_nb_cfg_report, &delay_nb_cfg_report); + ++ unixctl_command_register("debug/dump-local-bindings", "", 0, 0, ++ debug_dump_local_bindings, ++ &runtime_data->lbinding_data); ++ + unsigned int ovs_cond_seqno = UINT_MAX; + unsigned int ovnsb_cond_seqno = UINT_MAX; + unsigned int ovnsb_expected_cond_seqno = UINT_MAX; +@@ -2955,7 +2960,7 @@ main(int argc, char *argv[]) + ovnsb_cond_seqno, + ovnsb_expected_cond_seqno)); + if (runtime_data && ovs_idl_txn && ovnsb_idl_txn) { +- binding_seqno_run(&runtime_data->local_bindings); ++ binding_seqno_run(&runtime_data->lbinding_data); + } + + flow_output_data = engine_get_data(&en_flow_output); +@@ -2968,7 +2973,7 @@ main(int argc, char *argv[]) + } + ofctrl_seqno_run(ofctrl_get_cur_cfg()); + if (runtime_data && ovs_idl_txn && ovnsb_idl_txn) { +- binding_seqno_install(&runtime_data->local_bindings); ++ binding_seqno_install(&runtime_data->lbinding_data); + } + } + +@@ -3408,3 +3413,13 @@ debug_delay_nb_cfg_report(struct unixctl_conn *conn, int argc OVS_UNUSED, + unixctl_command_reply(conn, "no delay for nb_cfg report."); + } + } ++ ++static void ++debug_dump_local_bindings(struct unixctl_conn *conn, int argc OVS_UNUSED, ++ const char *argv[] OVS_UNUSED, void *local_bindings) ++{ ++ struct ds binding_data = DS_EMPTY_INITIALIZER; ++ binding_dump_local_bindings(local_bindings, &binding_data); ++ unixctl_command_reply(conn, ds_cstr(&binding_data)); ++ ds_destroy(&binding_data); ++} +diff --git a/controller/physical.c b/controller/physical.c +index fa5d0d692..874d1ee27 100644 +--- a/controller/physical.c ++++ b/controller/physical.c +@@ -1839,20 +1839,19 @@ physical_handle_ovs_iface_changes(struct physical_ctx *p_ctx, + continue; + } + +- const struct local_binding *lb = +- local_binding_find(p_ctx->local_bindings, iface_id); +- +- if (!lb || !lb->pb) { ++ const struct sbrec_port_binding *lb_pb = ++ local_binding_get_primary_pb(p_ctx->local_bindings, iface_id); ++ if (!lb_pb) { + continue; + } + + int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport : 0; + if (ovsrec_interface_is_deleted(iface_rec)) { +- ofctrl_remove_flows(flow_table, &lb->pb->header_.uuid); ++ ofctrl_remove_flows(flow_table, &lb_pb->header_.uuid); + simap_find_and_delete(&localvif_to_ofport, iface_id); + } else { + if (!ovsrec_interface_is_new(iface_rec)) { +- ofctrl_remove_flows(flow_table, &lb->pb->header_.uuid); ++ ofctrl_remove_flows(flow_table, &lb_pb->header_.uuid); + } + + simap_put(&localvif_to_ofport, iface_id, ofport); +@@ -1860,7 +1859,7 @@ physical_handle_ovs_iface_changes(struct physical_ctx *p_ctx, + p_ctx->mff_ovn_geneve, p_ctx->ct_zones, + p_ctx->active_tunnels, + p_ctx->local_datapaths, +- lb->pb, p_ctx->chassis, ++ lb_pb, p_ctx->chassis, + flow_table, &ofpacts); + } + } +diff --git a/controller/pinctrl.c b/controller/pinctrl.c +index b42288ea5..523a45b9a 100644 +--- a/controller/pinctrl.c ++++ b/controller/pinctrl.c +@@ -4240,6 +4240,12 @@ send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn, + struct shash *nat_addresses) + { + volatile struct garp_rarp_data *garp_rarp = NULL; ++ ++ /* Skip localports as they don't need to be announced */ ++ if (!strcmp(binding_rec->type, "localport")) { ++ return; ++ } ++ + /* Update GARP for NAT IP if it exists. Consider port bindings with type + * "l3gateway" for logical switch ports attached to gateway routers, and + * port bindings with type "patch" for logical switch ports attached to +diff --git a/debian/changelog b/debian/changelog +index 51f9bcc91..25a04f8ae 100644 +--- a/debian/changelog ++++ b/debian/changelog +@@ -1,3 +1,9 @@ ++ovn (21.03.1-1) unstable; urgency=low ++ ++ * New upstream version ++ ++ -- OVN team Fri, 12 Mar 2021 12:00:00 -0500 ++ + ovn (21.03.0-1) unstable; urgency=low + + * New upstream version +diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h +index 017176f98..d44b30b30 100644 +--- a/include/ovn/logical-fields.h ++++ b/include/ovn/logical-fields.h +@@ -66,6 +66,7 @@ enum mff_log_flags_bits { + MLF_LOOKUP_MAC_BIT = 6, + MLF_LOOKUP_LB_HAIRPIN_BIT = 7, + MLF_LOOKUP_FDB_BIT = 8, ++ MLF_SKIP_SNAT_FOR_LB_BIT = 9, + }; + + /* MFF_LOG_FLAGS_REG flag assignments */ +@@ -102,6 +103,10 @@ enum mff_log_flags { + + /* Indicate that the lookup in the fdb table was successful. */ + MLF_LOOKUP_FDB = (1 << MLF_LOOKUP_FDB_BIT), ++ ++ /* Indicate that a packet must not SNAT in the gateway router when ++ * load-balancing has taken place. */ ++ MLF_SKIP_SNAT_FOR_LB = (1 << MLF_SKIP_SNAT_FOR_LB_BIT), + }; + + /* OVN logical fields +diff --git a/lib/inc-proc-eng.c b/lib/inc-proc-eng.c +index 916dbbe39..a6337a1d9 100644 +--- a/lib/inc-proc-eng.c ++++ b/lib/inc-proc-eng.c +@@ -27,6 +27,7 @@ + #include "openvswitch/hmap.h" + #include "openvswitch/vlog.h" + #include "inc-proc-eng.h" ++#include "unixctl.h" + + VLOG_DEFINE_THIS_MODULE(inc_proc_eng); + +@@ -102,6 +103,40 @@ engine_get_nodes(struct engine_node *node, size_t *n_count) + return engine_topo_sort(node, NULL, n_count, &n_size); + } + ++static void ++engine_clear_stats(struct unixctl_conn *conn, int argc OVS_UNUSED, ++ const char *argv[] OVS_UNUSED, void *arg OVS_UNUSED) ++{ ++ for (size_t i = 0; i < engine_n_nodes; i++) { ++ struct engine_node *node = engine_nodes[i]; ++ ++ memset(&node->stats, 0, sizeof node->stats); ++ } ++ unixctl_command_reply(conn, NULL); ++} ++ ++static void ++engine_dump_stats(struct unixctl_conn *conn, int argc OVS_UNUSED, ++ const char *argv[] OVS_UNUSED, void *arg OVS_UNUSED) ++{ ++ struct ds dump = DS_EMPTY_INITIALIZER; ++ ++ for (size_t i = 0; i < engine_n_nodes; i++) { ++ struct engine_node *node = engine_nodes[i]; ++ ++ ds_put_format(&dump, ++ "Node: %s\n" ++ "- recompute: %12"PRIu64"\n" ++ "- compute: %12"PRIu64"\n" ++ "- abort: %12"PRIu64"\n", ++ node->name, node->stats.recompute, ++ node->stats.compute, node->stats.abort); ++ } ++ unixctl_command_reply(conn, ds_cstr(&dump)); ++ ++ ds_destroy(&dump); ++} ++ + void + engine_init(struct engine_node *node, struct engine_arg *arg) + { +@@ -115,6 +150,11 @@ engine_init(struct engine_node *node, struct engine_arg *arg) + engine_nodes[i]->data = NULL; + } + } ++ ++ unixctl_command_register("inc-engine/show-stats", "", 0, 0, ++ engine_dump_stats, NULL); ++ unixctl_command_register("inc-engine/clear-stats", "", 0, 0, ++ engine_clear_stats, NULL); + } + + void +@@ -288,6 +328,7 @@ engine_recompute(struct engine_node *node, bool forced, bool allowed) + + /* Run the node handler which might change state. */ + node->run(node, node->data); ++ node->stats.recompute++; + } + + /* Return true if the node could be computed, false otherwise. */ +@@ -312,6 +353,8 @@ engine_compute(struct engine_node *node, bool recompute_allowed) + } + } + } ++ node->stats.compute++; ++ + return true; + } + +@@ -321,6 +364,7 @@ engine_run_node(struct engine_node *node, bool recompute_allowed) + if (!node->n_inputs) { + /* Run the node handler which might change state. */ + node->run(node, node->data); ++ node->stats.recompute++; + return; + } + +@@ -377,6 +421,7 @@ engine_run(bool recompute_allowed) + engine_run_node(engine_nodes[i], recompute_allowed); + + if (engine_nodes[i]->state == EN_ABORTED) { ++ engine_nodes[i]->stats.abort++; + engine_run_aborted = true; + return; + } +@@ -393,6 +438,7 @@ engine_need_run(void) + } + + engine_nodes[i]->run(engine_nodes[i], engine_nodes[i]->data); ++ engine_nodes[i]->stats.recompute++; + VLOG_DBG("input node: %s, state: %s", engine_nodes[i]->name, + engine_node_state_name[engine_nodes[i]->state]); + if (engine_nodes[i]->state == EN_UPDATED) { +diff --git a/lib/inc-proc-eng.h b/lib/inc-proc-eng.h +index 857234677..7e9f5bb70 100644 +--- a/lib/inc-proc-eng.h ++++ b/lib/inc-proc-eng.h +@@ -107,6 +107,12 @@ enum engine_node_state { + EN_STATE_MAX, + }; + ++struct engine_stats { ++ uint64_t recompute; ++ uint64_t compute; ++ uint64_t abort; ++}; ++ + struct engine_node { + /* A unique name for each node. */ + char *name; +@@ -154,6 +160,9 @@ struct engine_node { + /* Method to clear up tracked data maintained by the engine node in the + * engine 'data'. It may be NULL. */ + void (*clear_tracked_data)(void *tracked_data); ++ ++ /* Engine stats. */ ++ struct engine_stats stats; + }; + + /* Initialize the data for the engine nodes. It calls each node's +diff --git a/lib/logical-fields.c b/lib/logical-fields.c +index 9d08b44c2..72853013e 100644 +--- a/lib/logical-fields.c ++++ b/lib/logical-fields.c +@@ -121,6 +121,10 @@ ovn_init_symtab(struct shash *symtab) + MLF_FORCE_SNAT_FOR_LB_BIT); + expr_symtab_add_subfield(symtab, "flags.force_snat_for_lb", NULL, + flags_str); ++ snprintf(flags_str, sizeof flags_str, "flags[%d]", ++ MLF_SKIP_SNAT_FOR_LB_BIT); ++ expr_symtab_add_subfield(symtab, "flags.skip_snat_for_lb", NULL, ++ flags_str); + + /* Connection tracking state. */ + expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false, +diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml +index c272cc922..3300f7180 100644 +--- a/northd/ovn-northd.8.xml ++++ b/northd/ovn-northd.8.xml +@@ -2720,7 +2720,11 @@ icmp6 { + (and optional port numbers) to load balance to. If the router is + configured to force SNAT any load-balanced packets, the above action + will be replaced by flags.force_snat_for_lb = 1; +- ct_lb(args);. If health check is enabled, then ++ ct_lb(args);. ++ If the load balancing rule is configured with skip_snat ++ set to true, the above action will be replaced by ++ flags.skip_snat_for_lb = 1; ct_lb(args);. ++ If health check is enabled, then + args will only contain those endpoints whose service + monitor status entry in OVN_Southbound db is + either online or empty. +@@ -2737,6 +2741,9 @@ icmp6 { + with an action of ct_dnat;. If the router is + configured to force SNAT any load-balanced packets, the above action + will be replaced by flags.force_snat_for_lb = 1; ct_dnat;. ++ If the load balancing rule is configured with skip_snat ++ set to true, the above action will be replaced by ++ flags.skip_snat_for_lb = 1; ct_dnat;. + + +
  • +@@ -2751,6 +2758,9 @@ icmp6 { + to force SNAT any load-balanced packets, the above action will be + replaced by flags.force_snat_for_lb = 1; + ct_lb(args);. ++ If the load balancing rule is configured with skip_snat ++ set to true, the above action will be replaced by ++ flags.skip_snat_for_lb = 1; ct_lb(args);. +
  • + +
  • +@@ -2763,6 +2773,9 @@ icmp6 { + If the router is configured to force SNAT any load-balanced + packets, the above action will be replaced by + flags.force_snat_for_lb = 1; ct_dnat;. ++ If the load balancing rule is configured with skip_snat ++ set to true, the above action will be replaced by ++ flags.skip_snat_for_lb = 1; ct_dnat;. +
  • + +
  • +@@ -3795,6 +3808,15 @@ nd_ns { +

    +
  • + ++
  • ++

    ++ If a load balancer configured to skip snat has been applied to ++ the Gateway router pipeline, a priority-120 flow matches ++ flags.skip_snat_for_lb == 1 && ip with an ++ action next;. ++

    ++
  • ++ +
  • +

    + If the Gateway router in the OVN Northbound database has been +diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c +index 5a2018c2e..4e406c594 100644 +--- a/northd/ovn-northd.c ++++ b/northd/ovn-northd.c +@@ -8573,10 +8573,16 @@ get_force_snat_ip(struct ovn_datapath *od, const char *key_type, + return true; + } + ++enum lb_snat_type { ++ NO_FORCE_SNAT, ++ FORCE_SNAT, ++ SKIP_SNAT, ++}; ++ + static void + add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, + struct ds *match, struct ds *actions, int priority, +- bool force_snat_for_lb, struct ovn_lb_vip *lb_vip, ++ enum lb_snat_type snat_type, struct ovn_lb_vip *lb_vip, + const char *proto, struct nbrec_load_balancer *lb, + struct shash *meter_groups, struct sset *nat_entries) + { +@@ -8585,9 +8591,10 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, + + /* A match and actions for new connections. */ + char *new_match = xasprintf("ct.new && %s", ds_cstr(match)); +- if (force_snat_for_lb) { +- char *new_actions = xasprintf("flags.force_snat_for_lb = 1; %s", +- ds_cstr(actions)); ++ if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) { ++ char *new_actions = xasprintf("flags.%s_snat_for_lb = 1; %s", ++ snat_type == SKIP_SNAT ? "skip" : "force", ++ ds_cstr(actions)); + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority, + new_match, new_actions, &lb->header_); + free(new_actions); +@@ -8598,11 +8605,12 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, + + /* A match and actions for established connections. */ + char *est_match = xasprintf("ct.est && %s", ds_cstr(match)); +- if (force_snat_for_lb) { ++ if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) { ++ char *est_actions = xasprintf("flags.%s_snat_for_lb = 1; ct_dnat;", ++ snat_type == SKIP_SNAT ? "skip" : "force"); + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority, +- est_match, +- "flags.force_snat_for_lb = 1; ct_dnat;", +- &lb->header_); ++ est_match, est_actions, &lb->header_); ++ free(est_actions); + } else { + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority, + est_match, "ct_dnat;", &lb->header_); +@@ -8675,11 +8683,13 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, + ds_put_format(&undnat_match, ") && outport == %s && " + "is_chassis_resident(%s)", od->l3dgw_port->json_key, + od->l3redirect_port->json_key); +- if (force_snat_for_lb) { ++ if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) { ++ char *action = xasprintf("flags.%s_snat_for_lb = 1; ct_dnat;", ++ snat_type == SKIP_SNAT ? "skip" : "force"); + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120, +- ds_cstr(&undnat_match), +- "flags.force_snat_for_lb = 1; ct_dnat;", ++ ds_cstr(&undnat_match), action, + &lb->header_); ++ free(action); + } else { + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120, + ds_cstr(&undnat_match), "ct_dnat;", +@@ -8689,6 +8699,105 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, + ds_destroy(&undnat_match); + } + ++static void ++build_lrouter_lb_flows(struct hmap *lflows, struct ovn_datapath *od, ++ struct hmap *lbs, struct shash *meter_groups, ++ struct sset *nat_entries, struct ds *match, ++ struct ds *actions) ++{ ++ /* A set to hold all ips that need defragmentation and tracking. */ ++ struct sset all_ips = SSET_INITIALIZER(&all_ips); ++ bool lb_force_snat_ip = ++ !lport_addresses_is_empty(&od->lb_force_snat_addrs); ++ ++ for (int i = 0; i < od->nbr->n_load_balancer; i++) { ++ struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i]; ++ struct ovn_northd_lb *lb = ++ ovn_northd_lb_find(lbs, &nb_lb->header_.uuid); ++ ovs_assert(lb); ++ ++ bool lb_skip_snat = smap_get_bool(&nb_lb->options, "skip_snat", false); ++ if (lb_skip_snat) { ++ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, ++ "flags.skip_snat_for_lb == 1 && ip", "next;"); ++ } ++ ++ for (size_t j = 0; j < lb->n_vips; j++) { ++ struct ovn_lb_vip *lb_vip = &lb->vips[j]; ++ struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j]; ++ ds_clear(actions); ++ build_lb_vip_actions(lb_vip, lb_vip_nb, actions, ++ lb->selection_fields, false); ++ ++ if (!sset_contains(&all_ips, lb_vip->vip_str)) { ++ sset_add(&all_ips, lb_vip->vip_str); ++ /* If there are any load balancing rules, we should send ++ * the packet to conntrack for defragmentation and ++ * tracking. This helps with two things. ++ * ++ * 1. With tracking, we can send only new connections to ++ * pick a DNAT ip address from a group. ++ * 2. If there are L4 ports in load balancing rules, we ++ * need the defragmentation to match on L4 ports. */ ++ ds_clear(match); ++ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { ++ ds_put_format(match, "ip && ip4.dst == %s", ++ lb_vip->vip_str); ++ } else { ++ ds_put_format(match, "ip && ip6.dst == %s", ++ lb_vip->vip_str); ++ } ++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, ++ 100, ds_cstr(match), "ct_next;", ++ &nb_lb->header_); ++ } ++ ++ /* Higher priority rules are added for load-balancing in DNAT ++ * table. For every match (on a VIP[:port]), we add two flows ++ * via add_router_lb_flow(). One flow is for specific matching ++ * on ct.new with an action of "ct_lb($targets);". The other ++ * flow is for ct.est with an action of "ct_dnat;". */ ++ ds_clear(match); ++ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { ++ ds_put_format(match, "ip && ip4.dst == %s", ++ lb_vip->vip_str); ++ } else { ++ ds_put_format(match, "ip && ip6.dst == %s", ++ lb_vip->vip_str); ++ } ++ ++ int prio = 110; ++ bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp"); ++ bool is_sctp = nullable_string_is_equal(nb_lb->protocol, ++ "sctp"); ++ const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp"; ++ ++ if (lb_vip->vip_port) { ++ ds_put_format(match, " && %s && %s.dst == %d", proto, ++ proto, lb_vip->vip_port); ++ prio = 120; ++ } ++ ++ if (od->l3redirect_port && ++ (lb_vip->n_backends || !lb_vip->empty_backend_rej)) { ++ ds_put_format(match, " && is_chassis_resident(%s)", ++ od->l3redirect_port->json_key); ++ } ++ ++ enum lb_snat_type snat_type = NO_FORCE_SNAT; ++ if (lb_skip_snat) { ++ snat_type = SKIP_SNAT; ++ } else if (lb_force_snat_ip || od->lb_force_snat_router_ip) { ++ snat_type = FORCE_SNAT; ++ } ++ add_router_lb_flow(lflows, od, match, actions, prio, ++ snat_type, lb_vip, proto, nb_lb, ++ meter_groups, nat_entries); ++ } ++ } ++ sset_destroy(&all_ips); ++} ++ + #define ND_RA_MAX_INTERVAL_MAX 1800 + #define ND_RA_MAX_INTERVAL_MIN 4 + +@@ -11002,668 +11111,643 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op, + } + } + +-/* NAT, Defrag and load balancing. */ + static void +-build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, +- struct hmap *lflows, +- struct shash *meter_groups, +- struct hmap *lbs, +- struct ds *match, struct ds *actions) ++build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od, ++ const struct nbrec_nat *nat, struct ds *match, ++ struct ds *actions, bool distributed, bool is_v6) + { +- if (od->nbr) { ++ /* Ingress UNSNAT table: It is for already established connections' ++ * reverse traffic. i.e., SNAT has already been done in egress ++ * pipeline and now the packet has entered the ingress pipeline as ++ * part of a reply. We undo the SNAT here. ++ * ++ * Undoing SNAT has to happen before DNAT processing. This is ++ * because when the packet was DNATed in ingress pipeline, it did ++ * not know about the possibility of eventual additional SNAT in ++ * egress pipeline. */ ++ if (strcmp(nat->type, "snat") && strcmp(nat->type, "dnat_and_snat")) { ++ return; ++ } + +- /* Packets are allowed by default. */ +- ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;"); +- ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;"); +- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;"); +- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;"); +- ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;"); +- ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;"); +- ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;"); +- +- /* Send the IPv6 NS packets to next table. When ovn-controller +- * generates IPv6 NS (for the action - nd_ns{}), the injected +- * packet would go through conntrack - which is not required. */ +- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;"); +- +- /* NAT rules are only valid on Gateway routers and routers with +- * l3dgw_port (router has a port with gateway chassis +- * specified). */ +- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { +- return; ++ bool stateless = lrouter_nat_is_stateless(nat); ++ if (!od->l3dgw_port) { ++ /* Gateway router. */ ++ ds_clear(match); ++ ds_clear(actions); ++ ds_put_format(match, "ip && ip%s.dst == %s", ++ is_v6 ? "6" : "4", nat->external_ip); ++ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { ++ ds_put_format(actions, "ip%s.dst=%s; next;", ++ is_v6 ? "6" : "4", nat->logical_ip); ++ } else { ++ ds_put_cstr(actions, "ct_snat;"); + } + +- struct sset nat_entries = SSET_INITIALIZER(&nat_entries); ++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, ++ 90, ds_cstr(match), ds_cstr(actions), ++ &nat->header_); ++ } else { ++ /* Distributed router. */ + +- bool dnat_force_snat_ip = +- !lport_addresses_is_empty(&od->dnat_force_snat_addrs); +- bool lb_force_snat_ip = +- !lport_addresses_is_empty(&od->lb_force_snat_addrs); ++ /* Traffic received on l3dgw_port is subject to NAT. */ ++ ds_clear(match); ++ ds_clear(actions); ++ ds_put_format(match, "ip && ip%s.dst == %s && inport == %s", ++ is_v6 ? "6" : "4", nat->external_ip, ++ od->l3dgw_port->json_key); ++ if (!distributed && od->l3redirect_port) { ++ /* Flows for NAT rules that are centralized are only ++ * programmed on the gateway chassis. */ ++ ds_put_format(match, " && is_chassis_resident(%s)", ++ od->l3redirect_port->json_key); ++ } + +- for (int i = 0; i < od->nbr->n_nat; i++) { +- const struct nbrec_nat *nat; ++ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { ++ ds_put_format(actions, "ip%s.dst=%s; next;", ++ is_v6 ? "6" : "4", nat->logical_ip); ++ } else { ++ ds_put_cstr(actions, "ct_snat;"); ++ } + +- nat = od->nbr->nat[i]; ++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, ++ 100, ds_cstr(match), ds_cstr(actions), ++ &nat->header_); ++ } ++} + +- ovs_be32 ip, mask; +- struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT; +- bool is_v6 = false; +- bool stateless = lrouter_nat_is_stateless(nat); +- struct nbrec_address_set *allowed_ext_ips = +- nat->allowed_ext_ips; +- struct nbrec_address_set *exempted_ext_ips = +- nat->exempted_ext_ips; ++static void ++build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od, ++ const struct nbrec_nat *nat, struct ds *match, ++ struct ds *actions, bool distributed, ++ ovs_be32 mask, bool is_v6) ++{ ++ /* Ingress DNAT table: Packets enter the pipeline with destination ++ * IP address that needs to be DNATted from a external IP address ++ * to a logical IP address. */ ++ if (!strcmp(nat->type, "dnat") || !strcmp(nat->type, "dnat_and_snat")) { ++ bool stateless = lrouter_nat_is_stateless(nat); + +- if (allowed_ext_ips && exempted_ext_ips) { +- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); +- VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since " +- "both allowed and exempt external ips set", +- UUID_ARGS(&(nat->header_.uuid))); +- continue; ++ if (!od->l3dgw_port) { ++ /* Gateway router. */ ++ /* Packet when it goes from the initiator to destination. ++ * We need to set flags.loopback because the router can ++ * send the packet back through the same interface. */ ++ ds_clear(match); ++ ds_put_format(match, "ip && ip%s.dst == %s", ++ is_v6 ? "6" : "4", nat->external_ip); ++ ds_clear(actions); ++ if (nat->allowed_ext_ips || nat->exempted_ext_ips) { ++ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, ++ is_v6, true, mask); + } + +- char *error = ip_parse_masked(nat->external_ip, &ip, &mask); +- if (error || mask != OVS_BE32_MAX) { +- free(error); +- error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6); +- if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) { +- /* Invalid for both IPv4 and IPv6 */ +- static struct vlog_rate_limit rl = +- VLOG_RATE_LIMIT_INIT(5, 1); +- VLOG_WARN_RL(&rl, "bad external ip %s for nat", +- nat->external_ip); +- free(error); +- continue; +- } +- /* It was an invalid IPv4 address, but valid IPv6. +- * Treat the rest of the handling of this NAT rule +- * as IPv6. */ +- is_v6 = true; +- } +- +- /* Check the validity of nat->logical_ip. 'logical_ip' can +- * be a subnet when the type is "snat". */ +- int cidr_bits; +- if (is_v6) { +- error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6); +- cidr_bits = ipv6_count_cidr_bits(&mask_v6); +- } else { +- error = ip_parse_masked(nat->logical_ip, &ip, &mask); +- cidr_bits = ip_count_cidr_bits(mask); ++ if (!lport_addresses_is_empty(&od->dnat_force_snat_addrs)) { ++ /* Indicate to the future tables that a DNAT has taken ++ * place and a force SNAT needs to be done in the ++ * Egress SNAT table. */ ++ ds_put_format(actions, "flags.force_snat_for_dnat = 1; "); + } +- if (!strcmp(nat->type, "snat")) { +- if (error) { +- /* Invalid for both IPv4 and IPv6 */ +- static struct vlog_rate_limit rl = +- VLOG_RATE_LIMIT_INIT(5, 1); +- VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat " +- "in router "UUID_FMT"", +- nat->logical_ip, UUID_ARGS(&od->key)); +- free(error); +- continue; +- } ++ ++ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { ++ ds_put_format(actions, "flags.loopback = 1; " ++ "ip%s.dst=%s; next;", ++ is_v6 ? "6" : "4", nat->logical_ip); + } else { +- if (error || (!is_v6 && mask != OVS_BE32_MAX) +- || (is_v6 && memcmp(&mask_v6, &v6_exact, +- sizeof mask_v6))) { +- /* Invalid for both IPv4 and IPv6 */ +- static struct vlog_rate_limit rl = +- VLOG_RATE_LIMIT_INIT(5, 1); +- VLOG_WARN_RL(&rl, "bad ip %s for dnat in router " +- ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key)); +- free(error); +- continue; ++ ds_put_format(actions, "flags.loopback = 1; ct_dnat(%s", ++ nat->logical_ip); ++ ++ if (nat->external_port_range[0]) { ++ ds_put_format(actions, ",%s", nat->external_port_range); + } ++ ds_put_format(actions, ");"); + } + +- /* For distributed router NAT, determine whether this NAT rule +- * satisfies the conditions for distributed NAT processing. */ +- bool distributed = false; +- struct eth_addr mac; +- if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") && +- nat->logical_port && nat->external_mac) { +- if (eth_addr_from_string(nat->external_mac, &mac)) { +- distributed = true; +- } else { +- static struct vlog_rate_limit rl = +- VLOG_RATE_LIMIT_INIT(5, 1); +- VLOG_WARN_RL(&rl, "bad mac %s for dnat in router " +- ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key)); +- continue; ++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, ++ ds_cstr(match), ds_cstr(actions), ++ &nat->header_); ++ } else { ++ /* Distributed router. */ ++ ++ /* Traffic received on l3dgw_port is subject to NAT. */ ++ ds_clear(match); ++ ds_put_format(match, "ip && ip%s.dst == %s && inport == %s", ++ is_v6 ? "6" : "4", nat->external_ip, ++ od->l3dgw_port->json_key); ++ if (!distributed && od->l3redirect_port) { ++ /* Flows for NAT rules that are centralized are only ++ * programmed on the gateway chassis. */ ++ ds_put_format(match, " && is_chassis_resident(%s)", ++ od->l3redirect_port->json_key); ++ } ++ ds_clear(actions); ++ if (nat->allowed_ext_ips || nat->exempted_ext_ips) { ++ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, ++ is_v6, true, mask); ++ } ++ ++ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { ++ ds_put_format(actions, "ip%s.dst=%s; next;", ++ is_v6 ? "6" : "4", nat->logical_ip); ++ } else { ++ ds_put_format(actions, "ct_dnat(%s", nat->logical_ip); ++ if (nat->external_port_range[0]) { ++ ds_put_format(actions, ",%s", nat->external_port_range); + } ++ ds_put_format(actions, ");"); + } + +- /* Ingress UNSNAT table: It is for already established connections' +- * reverse traffic. i.e., SNAT has already been done in egress +- * pipeline and now the packet has entered the ingress pipeline as +- * part of a reply. We undo the SNAT here. +- * +- * Undoing SNAT has to happen before DNAT processing. This is +- * because when the packet was DNATed in ingress pipeline, it did +- * not know about the possibility of eventual additional SNAT in +- * egress pipeline. */ +- if (!strcmp(nat->type, "snat") +- || !strcmp(nat->type, "dnat_and_snat")) { +- if (!od->l3dgw_port) { +- /* Gateway router. */ +- ds_clear(match); +- ds_clear(actions); +- ds_put_format(match, "ip && ip%s.dst == %s", +- is_v6 ? "6" : "4", +- nat->external_ip); +- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { +- ds_put_format(actions, "ip%s.dst=%s; next;", +- is_v6 ? "6" : "4", nat->logical_ip); +- } else { +- ds_put_cstr(actions, "ct_snat;"); +- } ++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, ++ ds_cstr(match), ds_cstr(actions), ++ &nat->header_); ++ } ++ } ++} + +- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, +- 90, ds_cstr(match), +- ds_cstr(actions), +- &nat->header_); +- } else { +- /* Distributed router. */ ++static void ++build_lrouter_out_undnat_flow(struct hmap *lflows, struct ovn_datapath *od, ++ const struct nbrec_nat *nat, struct ds *match, ++ struct ds *actions, bool distributed, ++ struct eth_addr mac, bool is_v6) ++{ ++ /* Egress UNDNAT table: It is for already established connections' ++ * reverse traffic. i.e., DNAT has already been done in ingress ++ * pipeline and now the packet has entered the egress pipeline as ++ * part of a reply. We undo the DNAT here. ++ * ++ * Note that this only applies for NAT on a distributed router. ++ * Undo DNAT on a gateway router is done in the ingress DNAT ++ * pipeline stage. */ ++ if (!od->l3dgw_port || ++ (strcmp(nat->type, "dnat") && strcmp(nat->type, "dnat_and_snat"))) { ++ return; ++ } + +- /* Traffic received on l3dgw_port is subject to NAT. */ +- ds_clear(match); +- ds_clear(actions); +- ds_put_format(match, "ip && ip%s.dst == %s" +- " && inport == %s", +- is_v6 ? "6" : "4", +- nat->external_ip, +- od->l3dgw_port->json_key); +- if (!distributed && od->l3redirect_port) { +- /* Flows for NAT rules that are centralized are only +- * programmed on the gateway chassis. */ +- ds_put_format(match, " && is_chassis_resident(%s)", +- od->l3redirect_port->json_key); +- } ++ ds_clear(match); ++ ds_put_format(match, "ip && ip%s.src == %s && outport == %s", ++ is_v6 ? "6" : "4", nat->logical_ip, ++ od->l3dgw_port->json_key); ++ if (!distributed && od->l3redirect_port) { ++ /* Flows for NAT rules that are centralized are only ++ * programmed on the gateway chassis. */ ++ ds_put_format(match, " && is_chassis_resident(%s)", ++ od->l3redirect_port->json_key); ++ } ++ ds_clear(actions); ++ if (distributed) { ++ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", ++ ETH_ADDR_ARGS(mac)); ++ } + +- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { +- ds_put_format(actions, "ip%s.dst=%s; next;", +- is_v6 ? "6" : "4", nat->logical_ip); +- } else { +- ds_put_cstr(actions, "ct_snat;"); +- } ++ if (!strcmp(nat->type, "dnat_and_snat") && ++ lrouter_nat_is_stateless(nat)) { ++ ds_put_format(actions, "ip%s.src=%s; next;", ++ is_v6 ? "6" : "4", nat->external_ip); ++ } else { ++ ds_put_format(actions, "ct_dnat;"); ++ } + +- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, +- 100, +- ds_cstr(match), ds_cstr(actions), +- &nat->header_); +- } +- } ++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100, ++ ds_cstr(match), ds_cstr(actions), ++ &nat->header_); ++} + +- /* Ingress DNAT table: Packets enter the pipeline with destination +- * IP address that needs to be DNATted from a external IP address +- * to a logical IP address. */ +- if (!strcmp(nat->type, "dnat") +- || !strcmp(nat->type, "dnat_and_snat")) { +- if (!od->l3dgw_port) { +- /* Gateway router. */ +- /* Packet when it goes from the initiator to destination. +- * We need to set flags.loopback because the router can +- * send the packet back through the same interface. */ +- ds_clear(match); +- ds_put_format(match, "ip && ip%s.dst == %s", +- is_v6 ? "6" : "4", +- nat->external_ip); +- ds_clear(actions); +- if (allowed_ext_ips || exempted_ext_ips) { +- lrouter_nat_add_ext_ip_match(od, lflows, match, nat, +- is_v6, true, mask); +- } ++static void ++build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od, ++ const struct nbrec_nat *nat, struct ds *match, ++ struct ds *actions, bool distributed, ++ struct eth_addr mac, ovs_be32 mask, ++ int cidr_bits, bool is_v6) ++{ ++ /* Egress SNAT table: Packets enter the egress pipeline with ++ * source ip address that needs to be SNATted to a external ip ++ * address. */ ++ if (strcmp(nat->type, "snat") && strcmp(nat->type, "dnat_and_snat")) { ++ return; ++ } + +- if (dnat_force_snat_ip) { +- /* Indicate to the future tables that a DNAT has taken +- * place and a force SNAT needs to be done in the +- * Egress SNAT table. */ +- ds_put_format(actions, +- "flags.force_snat_for_dnat = 1; "); +- } ++ bool stateless = lrouter_nat_is_stateless(nat); ++ if (!od->l3dgw_port) { ++ /* Gateway router. */ ++ ds_clear(match); ++ ds_put_format(match, "ip && ip%s.src == %s", ++ is_v6 ? "6" : "4", nat->logical_ip); ++ ds_clear(actions); + +- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { +- ds_put_format(actions, "flags.loopback = 1; " +- "ip%s.dst=%s; next;", +- is_v6 ? "6" : "4", nat->logical_ip); +- } else { +- ds_put_format(actions, "flags.loopback = 1; " +- "ct_dnat(%s", nat->logical_ip); ++ if (nat->allowed_ext_ips || nat->exempted_ext_ips) { ++ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, ++ is_v6, false, mask); ++ } + +- if (nat->external_port_range[0]) { +- ds_put_format(actions, ",%s", +- nat->external_port_range); +- } +- ds_put_format(actions, ");"); +- } ++ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { ++ ds_put_format(actions, "ip%s.src=%s; next;", ++ is_v6 ? "6" : "4", nat->external_ip); ++ } else { ++ ds_put_format(actions, "ct_snat(%s", nat->external_ip); + +- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, +- ds_cstr(match), ds_cstr(actions), +- &nat->header_); +- } else { +- /* Distributed router. */ ++ if (nat->external_port_range[0]) { ++ ds_put_format(actions, ",%s", ++ nat->external_port_range); ++ } ++ ds_put_format(actions, ");"); ++ } + +- /* Traffic received on l3dgw_port is subject to NAT. */ +- ds_clear(match); +- ds_put_format(match, "ip && ip%s.dst == %s" +- " && inport == %s", +- is_v6 ? "6" : "4", +- nat->external_ip, +- od->l3dgw_port->json_key); +- if (!distributed && od->l3redirect_port) { +- /* Flows for NAT rules that are centralized are only +- * programmed on the gateway chassis. */ +- ds_put_format(match, " && is_chassis_resident(%s)", +- od->l3redirect_port->json_key); +- } +- ds_clear(actions); +- if (allowed_ext_ips || exempted_ext_ips) { +- lrouter_nat_add_ext_ip_match(od, lflows, match, nat, +- is_v6, true, mask); +- } ++ /* The priority here is calculated such that the ++ * nat->logical_ip with the longest mask gets a higher ++ * priority. */ ++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, ++ cidr_bits + 1, ds_cstr(match), ++ ds_cstr(actions), &nat->header_); ++ } else { ++ uint16_t priority = cidr_bits + 1; + +- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { +- ds_put_format(actions, "ip%s.dst=%s; next;", +- is_v6 ? "6" : "4", nat->logical_ip); +- } else { +- ds_put_format(actions, "ct_dnat(%s", nat->logical_ip); +- if (nat->external_port_range[0]) { +- ds_put_format(actions, ",%s", +- nat->external_port_range); +- } +- ds_put_format(actions, ");"); +- } ++ /* Distributed router. */ ++ ds_clear(match); ++ ds_put_format(match, "ip && ip%s.src == %s && outport == %s", ++ is_v6 ? "6" : "4", nat->logical_ip, ++ od->l3dgw_port->json_key); ++ if (!distributed && od->l3redirect_port) { ++ /* Flows for NAT rules that are centralized are only ++ * programmed on the gateway chassis. */ ++ priority += 128; ++ ds_put_format(match, " && is_chassis_resident(%s)", ++ od->l3redirect_port->json_key); ++ } ++ ds_clear(actions); + +- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, +- ds_cstr(match), ds_cstr(actions), +- &nat->header_); +- } +- } ++ if (nat->allowed_ext_ips || nat->exempted_ext_ips) { ++ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, ++ is_v6, false, mask); ++ } + +- /* ARP resolve for NAT IPs. */ +- if (od->l3dgw_port) { +- if (!strcmp(nat->type, "snat")) { +- ds_clear(match); +- ds_put_format( +- match, "inport == %s && %s == %s", +- od->l3dgw_port->json_key, +- is_v6 ? "ip6.src" : "ip4.src", nat->external_ip); +- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT, +- 120, ds_cstr(match), "next;", +- &nat->header_); +- } ++ if (distributed) { ++ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", ++ ETH_ADDR_ARGS(mac)); ++ } + +- if (!sset_contains(&nat_entries, nat->external_ip)) { +- ds_clear(match); +- ds_put_format( +- match, "outport == %s && %s == %s", +- od->l3dgw_port->json_key, +- is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4, ++ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { ++ ds_put_format(actions, "ip%s.src=%s; next;", ++ is_v6 ? "6" : "4", nat->external_ip); ++ } else { ++ ds_put_format(actions, "ct_snat(%s", + nat->external_ip); +- ds_clear(actions); +- ds_put_format( +- actions, "eth.dst = %s; next;", +- distributed ? nat->external_mac : +- od->l3dgw_port->lrp_networks.ea_s); +- ovn_lflow_add_with_hint(lflows, od, +- S_ROUTER_IN_ARP_RESOLVE, +- 100, ds_cstr(match), +- ds_cstr(actions), +- &nat->header_); +- sset_add(&nat_entries, nat->external_ip); +- } +- } else { +- /* Add the NAT external_ip to the nat_entries even for +- * gateway routers. This is required for adding load balancer +- * flows.*/ +- sset_add(&nat_entries, nat->external_ip); ++ if (nat->external_port_range[0]) { ++ ds_put_format(actions, ",%s", nat->external_port_range); + } ++ ds_put_format(actions, ");"); ++ } + +- /* Egress UNDNAT table: It is for already established connections' +- * reverse traffic. i.e., DNAT has already been done in ingress +- * pipeline and now the packet has entered the egress pipeline as +- * part of a reply. We undo the DNAT here. +- * +- * Note that this only applies for NAT on a distributed router. +- * Undo DNAT on a gateway router is done in the ingress DNAT +- * pipeline stage. */ +- if (od->l3dgw_port && (!strcmp(nat->type, "dnat") +- || !strcmp(nat->type, "dnat_and_snat"))) { +- ds_clear(match); +- ds_put_format(match, "ip && ip%s.src == %s" +- " && outport == %s", +- is_v6 ? "6" : "4", +- nat->logical_ip, +- od->l3dgw_port->json_key); +- if (!distributed && od->l3redirect_port) { +- /* Flows for NAT rules that are centralized are only +- * programmed on the gateway chassis. */ +- ds_put_format(match, " && is_chassis_resident(%s)", +- od->l3redirect_port->json_key); +- } +- ds_clear(actions); +- if (distributed) { +- ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", +- ETH_ADDR_ARGS(mac)); +- } ++ /* The priority here is calculated such that the ++ * nat->logical_ip with the longest mask gets a higher ++ * priority. */ ++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, ++ priority, ds_cstr(match), ++ ds_cstr(actions), &nat->header_); ++ } ++} + +- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { +- ds_put_format(actions, "ip%s.src=%s; next;", +- is_v6 ? "6" : "4", nat->external_ip); +- } else { +- ds_put_format(actions, "ct_dnat;"); +- } ++static void ++build_lrouter_ingress_flow(struct hmap *lflows, struct ovn_datapath *od, ++ const struct nbrec_nat *nat, struct ds *match, ++ struct ds *actions, struct eth_addr mac, ++ bool distributed, bool is_v6) ++{ ++ if (od->l3dgw_port && !strcmp(nat->type, "snat")) { ++ ds_clear(match); ++ ds_put_format( ++ match, "inport == %s && %s == %s", ++ od->l3dgw_port->json_key, ++ is_v6 ? "ip6.src" : "ip4.src", nat->external_ip); ++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT, ++ 120, ds_cstr(match), "next;", ++ &nat->header_); ++ } ++ /* Logical router ingress table 0: ++ * For NAT on a distributed router, add rules allowing ++ * ingress traffic with eth.dst matching nat->external_mac ++ * on the l3dgw_port instance where nat->logical_port is ++ * resident. */ ++ if (distributed) { ++ /* Store the ethernet address of the port receiving the packet. ++ * This will save us from having to match on inport further ++ * down in the pipeline. ++ */ ++ ds_clear(actions); ++ ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;", ++ od->l3dgw_port->lrp_networks.ea_s); + +- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100, +- ds_cstr(match), ds_cstr(actions), +- &nat->header_); +- } ++ ds_clear(match); ++ ds_put_format(match, ++ "eth.dst == "ETH_ADDR_FMT" && inport == %s" ++ " && is_chassis_resident(\"%s\")", ++ ETH_ADDR_ARGS(mac), ++ od->l3dgw_port->json_key, ++ nat->logical_port); ++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50, ++ ds_cstr(match), ds_cstr(actions), ++ &nat->header_); ++ } ++} + +- /* Egress SNAT table: Packets enter the egress pipeline with +- * source ip address that needs to be SNATted to a external ip +- * address. */ +- if (!strcmp(nat->type, "snat") +- || !strcmp(nat->type, "dnat_and_snat")) { +- if (!od->l3dgw_port) { +- /* Gateway router. */ +- ds_clear(match); +- ds_put_format(match, "ip && ip%s.src == %s", +- is_v6 ? "6" : "4", +- nat->logical_ip); +- ds_clear(actions); ++static int ++lrouter_check_nat_entry(struct ovn_datapath *od, const struct nbrec_nat *nat, ++ ovs_be32 *mask, bool *is_v6, int *cidr_bits, ++ struct eth_addr *mac, bool *distributed) ++{ ++ struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT; ++ ovs_be32 ip; + +- if (allowed_ext_ips || exempted_ext_ips) { +- lrouter_nat_add_ext_ip_match(od, lflows, match, nat, +- is_v6, false, mask); +- } ++ if (nat->allowed_ext_ips && nat->exempted_ext_ips) { ++ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); ++ VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since " ++ "both allowed and exempt external ips set", ++ UUID_ARGS(&(nat->header_.uuid))); ++ return -EINVAL; ++ } + +- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { +- ds_put_format(actions, "ip%s.src=%s; next;", +- is_v6 ? "6" : "4", nat->external_ip); +- } else { +- ds_put_format(actions, "ct_snat(%s", +- nat->external_ip); ++ char *error = ip_parse_masked(nat->external_ip, &ip, mask); ++ *is_v6 = false; + +- if (nat->external_port_range[0]) { +- ds_put_format(actions, ",%s", +- nat->external_port_range); +- } +- ds_put_format(actions, ");"); +- } ++ if (error || *mask != OVS_BE32_MAX) { ++ free(error); ++ error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6); ++ if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) { ++ /* Invalid for both IPv4 and IPv6 */ ++ static struct vlog_rate_limit rl = ++ VLOG_RATE_LIMIT_INIT(5, 1); ++ VLOG_WARN_RL(&rl, "bad external ip %s for nat", ++ nat->external_ip); ++ free(error); ++ return -EINVAL; ++ } ++ /* It was an invalid IPv4 address, but valid IPv6. ++ * Treat the rest of the handling of this NAT rule ++ * as IPv6. */ ++ *is_v6 = true; ++ } + +- /* The priority here is calculated such that the +- * nat->logical_ip with the longest mask gets a higher +- * priority. */ +- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, +- cidr_bits + 1, +- ds_cstr(match), ds_cstr(actions), +- &nat->header_); +- } else { +- uint16_t priority = cidr_bits + 1; ++ /* Check the validity of nat->logical_ip. 'logical_ip' can ++ * be a subnet when the type is "snat". */ ++ if (*is_v6) { ++ error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6); ++ *cidr_bits = ipv6_count_cidr_bits(&mask_v6); ++ } else { ++ error = ip_parse_masked(nat->logical_ip, &ip, mask); ++ *cidr_bits = ip_count_cidr_bits(*mask); ++ } ++ if (!strcmp(nat->type, "snat")) { ++ if (error) { ++ /* Invalid for both IPv4 and IPv6 */ ++ static struct vlog_rate_limit rl = ++ VLOG_RATE_LIMIT_INIT(5, 1); ++ VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat " ++ "in router "UUID_FMT"", ++ nat->logical_ip, UUID_ARGS(&od->key)); ++ free(error); ++ return -EINVAL; ++ } ++ } else { ++ if (error || (*is_v6 == false && *mask != OVS_BE32_MAX) ++ || (*is_v6 && memcmp(&mask_v6, &v6_exact, ++ sizeof mask_v6))) { ++ /* Invalid for both IPv4 and IPv6 */ ++ static struct vlog_rate_limit rl = ++ VLOG_RATE_LIMIT_INIT(5, 1); ++ VLOG_WARN_RL(&rl, "bad ip %s for dnat in router " ++ ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key)); ++ free(error); ++ return -EINVAL; ++ } ++ } + +- /* Distributed router. */ +- ds_clear(match); +- ds_put_format(match, "ip && ip%s.src == %s" +- " && outport == %s", +- is_v6 ? "6" : "4", +- nat->logical_ip, +- od->l3dgw_port->json_key); +- if (!distributed && od->l3redirect_port) { +- /* Flows for NAT rules that are centralized are only +- * programmed on the gateway chassis. */ +- priority += 128; +- ds_put_format(match, " && is_chassis_resident(%s)", +- od->l3redirect_port->json_key); +- } +- ds_clear(actions); ++ /* For distributed router NAT, determine whether this NAT rule ++ * satisfies the conditions for distributed NAT processing. */ ++ *distributed = false; ++ if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") && ++ nat->logical_port && nat->external_mac) { ++ if (eth_addr_from_string(nat->external_mac, mac)) { ++ *distributed = true; ++ } else { ++ static struct vlog_rate_limit rl = ++ VLOG_RATE_LIMIT_INIT(5, 1); ++ VLOG_WARN_RL(&rl, "bad mac %s for dnat in router " ++ ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key)); ++ return -EINVAL; ++ } ++ } + +- if (allowed_ext_ips || exempted_ext_ips) { +- lrouter_nat_add_ext_ip_match(od, lflows, match, nat, +- is_v6, false, mask); +- } ++ return 0; ++} + +- if (distributed) { +- ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", +- ETH_ADDR_ARGS(mac)); +- } ++/* NAT, Defrag and load balancing. */ ++static void ++build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, ++ struct hmap *lflows, ++ struct shash *meter_groups, ++ struct hmap *lbs, ++ struct ds *match, struct ds *actions) ++{ ++ if (!od->nbr) { ++ return; ++ } + +- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { +- ds_put_format(actions, "ip%s.src=%s; next;", +- is_v6 ? "6" : "4", nat->external_ip); +- } else { +- ds_put_format(actions, "ct_snat(%s", +- nat->external_ip); +- if (nat->external_port_range[0]) { +- ds_put_format(actions, ",%s", +- nat->external_port_range); +- } +- ds_put_format(actions, ");"); +- } ++ /* Packets are allowed by default. */ ++ ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;"); ++ ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;"); ++ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;"); ++ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;"); ++ ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;"); ++ ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;"); ++ ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;"); ++ ++ /* Send the IPv6 NS packets to next table. When ovn-controller ++ * generates IPv6 NS (for the action - nd_ns{}), the injected ++ * packet would go through conntrack - which is not required. */ ++ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;"); ++ ++ /* NAT rules are only valid on Gateway routers and routers with ++ * l3dgw_port (router has a port with gateway chassis ++ * specified). */ ++ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { ++ return; ++ } + +- /* The priority here is calculated such that the +- * nat->logical_ip with the longest mask gets a higher +- * priority. */ +- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, +- priority, ds_cstr(match), +- ds_cstr(actions), +- &nat->header_); +- } +- } ++ struct sset nat_entries = SSET_INITIALIZER(&nat_entries); + +- /* Logical router ingress table 0: +- * For NAT on a distributed router, add rules allowing +- * ingress traffic with eth.dst matching nat->external_mac +- * on the l3dgw_port instance where nat->logical_port is +- * resident. */ +- if (distributed) { +- /* Store the ethernet address of the port receiving the packet. +- * This will save us from having to match on inport further +- * down in the pipeline. +- */ +- ds_clear(actions); +- ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;", +- od->l3dgw_port->lrp_networks.ea_s); ++ bool dnat_force_snat_ip = ++ !lport_addresses_is_empty(&od->dnat_force_snat_addrs); ++ bool lb_force_snat_ip = ++ !lport_addresses_is_empty(&od->lb_force_snat_addrs); + +- ds_clear(match); +- ds_put_format(match, +- "eth.dst == "ETH_ADDR_FMT" && inport == %s" +- " && is_chassis_resident(\"%s\")", +- ETH_ADDR_ARGS(mac), +- od->l3dgw_port->json_key, +- nat->logical_port); +- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50, +- ds_cstr(match), ds_cstr(actions), +- &nat->header_); +- } ++ for (int i = 0; i < od->nbr->n_nat; i++) { ++ const struct nbrec_nat *nat = nat = od->nbr->nat[i]; ++ struct eth_addr mac = eth_addr_broadcast; ++ bool is_v6, distributed; ++ ovs_be32 mask; ++ int cidr_bits; + +- /* Ingress Gateway Redirect Table: For NAT on a distributed +- * router, add flows that are specific to a NAT rule. These +- * flows indicate the presence of an applicable NAT rule that +- * can be applied in a distributed manner. +- * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to +- * NAT external IP and NAT external mac so the ARP request +- * generated in the following stage is sent out with proper IP/MAC +- * src addresses. +- */ +- if (distributed) { +- ds_clear(match); +- ds_clear(actions); +- ds_put_format(match, +- "ip%s.src == %s && outport == %s && " +- "is_chassis_resident(\"%s\")", +- is_v6 ? "6" : "4", nat->logical_ip, +- od->l3dgw_port->json_key, nat->logical_port); +- ds_put_format(actions, "eth.src = %s; %s = %s; next;", +- nat->external_mac, +- is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4, +- nat->external_ip); +- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, +- 100, ds_cstr(match), +- ds_cstr(actions), &nat->header_); +- } ++ if (lrouter_check_nat_entry(od, nat, &mask, &is_v6, &cidr_bits, ++ &mac, &distributed) < 0) { ++ continue; ++ } + +- /* Egress Loopback table: For NAT on a distributed router. +- * If packets in the egress pipeline on the distributed +- * gateway port have ip.dst matching a NAT external IP, then +- * loop a clone of the packet back to the beginning of the +- * ingress pipeline with inport = outport. */ +- if (od->l3dgw_port) { +- /* Distributed router. */ +- ds_clear(match); +- ds_put_format(match, "ip%s.dst == %s && outport == %s", +- is_v6 ? "6" : "4", +- nat->external_ip, +- od->l3dgw_port->json_key); +- if (!distributed) { +- ds_put_format(match, " && is_chassis_resident(%s)", +- od->l3redirect_port->json_key); +- } else { +- ds_put_format(match, " && is_chassis_resident(\"%s\")", +- nat->logical_port); +- } ++ /* S_ROUTER_IN_UNSNAT */ ++ build_lrouter_in_unsnat_flow(lflows, od, nat, match, actions, distributed, ++ is_v6); ++ /* S_ROUTER_IN_DNAT */ ++ build_lrouter_in_dnat_flow(lflows, od, nat, match, actions, distributed, ++ mask, is_v6); + ++ /* ARP resolve for NAT IPs. */ ++ if (od->l3dgw_port) { ++ if (!sset_contains(&nat_entries, nat->external_ip)) { ++ ds_clear(match); ++ ds_put_format( ++ match, "outport == %s && %s == %s", ++ od->l3dgw_port->json_key, ++ is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4, ++ nat->external_ip); + ds_clear(actions); +- ds_put_format(actions, +- "clone { ct_clear; " +- "inport = outport; outport = \"\"; " +- "flags = 0; flags.loopback = 1; "); +- for (int j = 0; j < MFF_N_LOG_REGS; j++) { +- ds_put_format(actions, "reg%d = 0; ", j); +- } +- ds_put_format(actions, REGBIT_EGRESS_LOOPBACK" = 1; " +- "next(pipeline=ingress, table=%d); };", +- ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); +- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100, +- ds_cstr(match), ds_cstr(actions), ++ ds_put_format( ++ actions, "eth.dst = %s; next;", ++ distributed ? nat->external_mac : ++ od->l3dgw_port->lrp_networks.ea_s); ++ ovn_lflow_add_with_hint(lflows, od, ++ S_ROUTER_IN_ARP_RESOLVE, ++ 100, ds_cstr(match), ++ ds_cstr(actions), + &nat->header_); ++ sset_add(&nat_entries, nat->external_ip); + } +- } +- +- /* Handle force SNAT options set in the gateway router. */ +- if (!od->l3dgw_port) { +- if (dnat_force_snat_ip) { +- if (od->dnat_force_snat_addrs.n_ipv4_addrs) { +- build_lrouter_force_snat_flows(lflows, od, "4", +- od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s, +- "dnat"); +- } +- if (od->dnat_force_snat_addrs.n_ipv6_addrs) { +- build_lrouter_force_snat_flows(lflows, od, "6", +- od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s, +- "dnat"); +- } +- } +- if (lb_force_snat_ip) { +- if (od->lb_force_snat_addrs.n_ipv4_addrs) { +- build_lrouter_force_snat_flows(lflows, od, "4", +- od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb"); +- } +- if (od->lb_force_snat_addrs.n_ipv6_addrs) { +- build_lrouter_force_snat_flows(lflows, od, "6", +- od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb"); +- } ++ } else { ++ /* Add the NAT external_ip to the nat_entries even for ++ * gateway routers. This is required for adding load balancer ++ * flows.*/ ++ sset_add(&nat_entries, nat->external_ip); ++ } ++ ++ /* S_ROUTER_OUT_UNDNAT */ ++ build_lrouter_out_undnat_flow(lflows, od, nat, match, actions, distributed, ++ mac, is_v6); ++ /* S_ROUTER_OUT_SNAT */ ++ build_lrouter_out_snat_flow(lflows, od, nat, match, actions, distributed, ++ mac, mask, cidr_bits, is_v6); ++ ++ /* S_ROUTER_IN_ADMISSION - S_ROUTER_IN_IP_INPUT */ ++ build_lrouter_ingress_flow(lflows, od, nat, match, actions, ++ mac, distributed, is_v6); ++ ++ /* Ingress Gateway Redirect Table: For NAT on a distributed ++ * router, add flows that are specific to a NAT rule. These ++ * flows indicate the presence of an applicable NAT rule that ++ * can be applied in a distributed manner. ++ * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to ++ * NAT external IP and NAT external mac so the ARP request ++ * generated in the following stage is sent out with proper IP/MAC ++ * src addresses. ++ */ ++ if (distributed) { ++ ds_clear(match); ++ ds_clear(actions); ++ ds_put_format(match, ++ "ip%s.src == %s && outport == %s && " ++ "is_chassis_resident(\"%s\")", ++ is_v6 ? "6" : "4", nat->logical_ip, ++ od->l3dgw_port->json_key, nat->logical_port); ++ ds_put_format(actions, "eth.src = %s; %s = %s; next;", ++ nat->external_mac, ++ is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4, ++ nat->external_ip); ++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, ++ 100, ds_cstr(match), ++ ds_cstr(actions), &nat->header_); ++ } ++ ++ /* Egress Loopback table: For NAT on a distributed router. ++ * If packets in the egress pipeline on the distributed ++ * gateway port have ip.dst matching a NAT external IP, then ++ * loop a clone of the packet back to the beginning of the ++ * ingress pipeline with inport = outport. */ ++ if (od->l3dgw_port) { ++ /* Distributed router. */ ++ ds_clear(match); ++ ds_put_format(match, "ip%s.dst == %s && outport == %s", ++ is_v6 ? "6" : "4", ++ nat->external_ip, ++ od->l3dgw_port->json_key); ++ if (!distributed) { ++ ds_put_format(match, " && is_chassis_resident(%s)", ++ od->l3redirect_port->json_key); ++ } else { ++ ds_put_format(match, " && is_chassis_resident(\"%s\")", ++ nat->logical_port); + } + +- /* For gateway router, re-circulate every packet through +- * the DNAT zone. This helps with the following. +- * +- * Any packet that needs to be unDNATed in the reverse +- * direction gets unDNATed. Ideally this could be done in +- * the egress pipeline. But since the gateway router +- * does not have any feature that depends on the source +- * ip address being external IP address for IP routing, +- * we can do it here, saving a future re-circulation. */ +- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50, +- "ip", "flags.loopback = 1; ct_dnat;"); ++ ds_clear(actions); ++ ds_put_format(actions, ++ "clone { ct_clear; " ++ "inport = outport; outport = \"\"; " ++ "flags = 0; flags.loopback = 1; "); ++ for (int j = 0; j < MFF_N_LOG_REGS; j++) { ++ ds_put_format(actions, "reg%d = 0; ", j); ++ } ++ ds_put_format(actions, REGBIT_EGRESS_LOOPBACK" = 1; " ++ "next(pipeline=ingress, table=%d); };", ++ ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); ++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100, ++ ds_cstr(match), ds_cstr(actions), ++ &nat->header_); + } ++ } + +- /* Load balancing and packet defrag are only valid on +- * Gateway routers or router with gateway port. */ +- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { +- sset_destroy(&nat_entries); +- return; ++ /* Handle force SNAT options set in the gateway router. */ ++ if (!od->l3dgw_port) { ++ if (dnat_force_snat_ip) { ++ if (od->dnat_force_snat_addrs.n_ipv4_addrs) { ++ build_lrouter_force_snat_flows(lflows, od, "4", ++ od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s, ++ "dnat"); ++ } ++ if (od->dnat_force_snat_addrs.n_ipv6_addrs) { ++ build_lrouter_force_snat_flows(lflows, od, "6", ++ od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s, ++ "dnat"); ++ } + } +- +- /* A set to hold all ips that need defragmentation and tracking. */ +- struct sset all_ips = SSET_INITIALIZER(&all_ips); +- +- for (int i = 0; i < od->nbr->n_load_balancer; i++) { +- struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i]; +- struct ovn_northd_lb *lb = +- ovn_northd_lb_find(lbs, &nb_lb->header_.uuid); +- ovs_assert(lb); +- +- for (size_t j = 0; j < lb->n_vips; j++) { +- struct ovn_lb_vip *lb_vip = &lb->vips[j]; +- struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j]; +- ds_clear(actions); +- build_lb_vip_actions(lb_vip, lb_vip_nb, actions, +- lb->selection_fields, false); +- +- if (!sset_contains(&all_ips, lb_vip->vip_str)) { +- sset_add(&all_ips, lb_vip->vip_str); +- /* If there are any load balancing rules, we should send +- * the packet to conntrack for defragmentation and +- * tracking. This helps with two things. +- * +- * 1. With tracking, we can send only new connections to +- * pick a DNAT ip address from a group. +- * 2. If there are L4 ports in load balancing rules, we +- * need the defragmentation to match on L4 ports. */ +- ds_clear(match); +- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { +- ds_put_format(match, "ip && ip4.dst == %s", +- lb_vip->vip_str); +- } else { +- ds_put_format(match, "ip && ip6.dst == %s", +- lb_vip->vip_str); +- } +- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, +- 100, ds_cstr(match), "ct_next;", +- &nb_lb->header_); +- } +- +- /* Higher priority rules are added for load-balancing in DNAT +- * table. For every match (on a VIP[:port]), we add two flows +- * via add_router_lb_flow(). One flow is for specific matching +- * on ct.new with an action of "ct_lb($targets);". The other +- * flow is for ct.est with an action of "ct_dnat;". */ +- ds_clear(match); +- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { +- ds_put_format(match, "ip && ip4.dst == %s", +- lb_vip->vip_str); +- } else { +- ds_put_format(match, "ip && ip6.dst == %s", +- lb_vip->vip_str); +- } +- +- int prio = 110; +- bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp"); +- bool is_sctp = nullable_string_is_equal(nb_lb->protocol, +- "sctp"); +- const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp"; +- +- if (lb_vip->vip_port) { +- ds_put_format(match, " && %s && %s.dst == %d", proto, +- proto, lb_vip->vip_port); +- prio = 120; +- } +- +- if (od->l3redirect_port && +- (lb_vip->n_backends || !lb_vip->empty_backend_rej)) { +- ds_put_format(match, " && is_chassis_resident(%s)", +- od->l3redirect_port->json_key); +- } +- bool force_snat_for_lb = +- lb_force_snat_ip || od->lb_force_snat_router_ip; +- add_router_lb_flow(lflows, od, match, actions, prio, +- force_snat_for_lb, lb_vip, proto, +- nb_lb, meter_groups, &nat_entries); ++ if (lb_force_snat_ip) { ++ if (od->lb_force_snat_addrs.n_ipv4_addrs) { ++ build_lrouter_force_snat_flows(lflows, od, "4", ++ od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb"); ++ } ++ if (od->lb_force_snat_addrs.n_ipv6_addrs) { ++ build_lrouter_force_snat_flows(lflows, od, "6", ++ od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb"); + } + } +- sset_destroy(&all_ips); ++ ++ /* For gateway router, re-circulate every packet through ++ * the DNAT zone. This helps with the following. ++ * ++ * Any packet that needs to be unDNATed in the reverse ++ * direction gets unDNATed. Ideally this could be done in ++ * the egress pipeline. But since the gateway router ++ * does not have any feature that depends on the source ++ * ip address being external IP address for IP routing, ++ * we can do it here, saving a future re-circulation. */ ++ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50, ++ "ip", "flags.loopback = 1; ct_dnat;"); ++ } ++ ++ /* Load balancing and packet defrag are only valid on ++ * Gateway routers or router with gateway port. */ ++ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { + sset_destroy(&nat_entries); ++ return; + } ++ ++ build_lrouter_lb_flows(lflows, od, lbs, meter_groups, &nat_entries, ++ match, actions); ++ ++ sset_destroy(&nat_entries); + } + + +diff --git a/ovn-nb.xml b/ovn-nb.xml +index b0a4adffe..408c98090 100644 +--- a/ovn-nb.xml ++++ b/ovn-nb.xml +@@ -1653,6 +1653,12 @@ + exactly one IPv4 and/or one IPv6 address on it, separated by a space + character. + ++ ++ ++ If the load balancing rule is configured with skip_snat ++ option, the force_snat_for_lb option configured for the router ++ pipeline will not be applied for this load balancer. ++ + + + +diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at +index 2cd3e261f..5c64fff12 100644 +--- a/tests/ovn-controller.at ++++ b/tests/ovn-controller.at +@@ -431,3 +431,83 @@ OVS_WAIT_UNTIL([ + + OVN_CLEANUP([hv1]) + AT_CLEANUP ++ ++# Test that changes of a port binding from one type to another doesn'that ++# result in any ovn-controller asserts or crashes. ++AT_SETUP([ovn-controller - port binding type change handling]) ++AT_KEYWORDS([ovn]) ++ovn_start ++ ++net_add n1 ++sim_add hv1 ++ovs-vsctl add-br br-phys ++ovn_attach n1 br-phys 192.168.0.1 ++ ++check ovn-nbctl ls-add ls1 -- lsp-add ls1 lsp1 ++ ++as hv1 ++check ovs-vsctl \ ++ -- add-port br-int vif1 \ ++ -- set Interface vif1 external_ids:iface-id=lsp1 ++ ++# ovn-controller should bind the interface. ++wait_for_ports_up ++hv_uuid=$(fetch_column Chassis _uuid name=hv1) ++check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1 ++ ++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[1]] ++primary 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) ++for type in ${pb_types[[@]]} ++do ++ for update_type in ${pb_types[[@]]} ++ do ++ check ovn-sbctl set port_binding lsp1 type=$type ++ check as hv1 ovs-vsctl set open . external_ids:ovn-cms-options=$type ++ OVS_WAIT_UNTIL([test $type = $(ovn-sbctl get chassis . other_config:ovn-cms-options)]) ++ ++ AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[0]] ++---------------------------------------- ++]) ++ ++ echo "Updating to $update_type from $type" ++ check ovn-sbctl set port_binding lsp1 type=$update_type ++ check as hv1 ovs-vsctl set open . external_ids:ovn-cms-options=$update_type ++ OVS_WAIT_UNTIL([test $update_type = $(ovn-sbctl get chassis . other_config:ovn-cms-options)]) ++ ++ AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[0]] ++---------------------------------------- ++]) ++ # Set the port binding type back to VIF. ++ check ovn-sbctl set port_binding lsp1 type=\"\" ++ check as hv1 ovs-vsctl set open . external_ids:ovn-cms-options=foo ++ OVS_WAIT_UNTIL([test foo = $(ovn-sbctl get chassis . other_config:ovn-cms-options)]) ++ ++ AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[1]] ++primary lport : [[lsp1]] ++---------------------------------------- ++]) ++ done ++done ++ ++OVN_CLEANUP([hv1]) ++AT_CLEANUP +diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at +index b78baa708..6d5dce668 100644 +--- a/tests/ovn-northd.at ++++ b/tests/ovn-northd.at +@@ -2551,7 +2551,7 @@ wait_row_count nb:Logical_Switch_Port 1 up=false name=lsp1 + + AT_CLEANUP + +-AT_SETUP([ovn -- lb_force_snat_ip for Gateway Routers]) ++AT_SETUP([ovn -- Load Balancers and lb_force_snat_ip for Gateway Routers]) + ovn_start + + check ovn-nbctl ls-add sw0 +@@ -2589,11 +2589,11 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl + table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;) + ]) + +-AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl +-]) +- +- +-AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl ++AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl ++ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;) ++ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(ct_dnat;) ++ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(ct_lb(backends=10.0.0.4:8080);) ++ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;) + ]) + + check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="20.0.0.4 aef0::4" +@@ -2608,14 +2608,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl + table=5 (lr_in_unsnat ), priority=110 , match=(ip6 && ip6.dst == aef0::4), action=(ct_snat;) + ]) + +-AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl ++AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl ++ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;) + table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;) + table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);) ++ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;) + ]) + +-AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl ++AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl ++ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;) + table=1 (lr_out_snat ), priority=100 , match=(flags.force_snat_for_lb == 1 && ip4), action=(ct_snat(20.0.0.4);) + table=1 (lr_out_snat ), priority=100 , match=(flags.force_snat_for_lb == 1 && ip6), action=(ct_snat(aef0::4);) ++ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) + ]) + + check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="router_ip" +@@ -2633,15 +2637,19 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl + table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;) + ]) + +-AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl ++AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl ++ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;) + table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;) + table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);) ++ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;) + ]) + +-AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl ++AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl ++ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;) + table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);) + table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);) + table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);) ++ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) + ]) + + check ovn-nbctl --wait=sb remove logical_router lr0 options chassis +@@ -2653,7 +2661,9 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl + table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;) + ]) + +-AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl ++AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl ++ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;) ++ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) + ]) + + check ovn-nbctl set logical_router lr0 options:chassis=ch1 +@@ -2670,16 +2680,43 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl + table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip6.dst == bef0::1), action=(ct_snat;) + ]) + +-AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl ++AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl ++ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;) + table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;) + table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);) ++ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;) + ]) + +-AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl ++AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl ++ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;) + table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);) + table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);) + table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);) + table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip6 && outport == "lr0-sw1"), action=(ct_snat(bef0::1);) ++ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) ++]) ++ ++check ovn-nbctl --wait=sb lb-add lb2 10.0.0.20:80 10.0.0.40:8080 ++check ovn-nbctl --wait=sb set load_balancer lb2 options:skip_snat=true ++check ovn-nbctl lr-lb-add lr0 lb2 ++check ovn-nbctl --wait=sb lb-del lb1 ++ovn-sbctl dump-flows lr0 > lr0flows ++ ++AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl ++ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;) ++ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-public" && ip4.dst == 172.168.0.100), action=(ct_snat;) ++ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw0" && ip4.dst == 10.0.0.1), action=(ct_snat;) ++ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;) ++ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip6.dst == bef0::1), action=(ct_snat;) ++]) ++ ++AT_CHECK([grep "lr_in_dnat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl ++ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.20 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_dnat;) ++ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.20 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_lb(backends=10.0.0.40:8080);) ++]) ++ ++AT_CHECK([grep "lr_out_snat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl ++ table=1 (lr_out_snat ), priority=120 , match=(flags.skip_snat_for_lb == 1 && ip), action=(next;) + ]) + + AT_CLEANUP +diff --git a/tests/ovn.at b/tests/ovn.at +index b465784cd..dbc6e549b 100644 +--- a/tests/ovn.at ++++ b/tests/ovn.at +@@ -11494,6 +11494,59 @@ OVN_CLEANUP([hv1],[hv2]) + + AT_CLEANUP + ++AT_SETUP([ovn -- localport suppress gARP]) ++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 ++ ++check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys ++ ++check ovn-nbctl ls-add ls \ ++ -- lsp-add ls lp \ ++ -- lsp-set-type lp localport \ ++ -- lsp-set-addresses lp "00:00:00:00:00:01 10.0.0.1" \ ++ -- lsp-add ls ln \ ++ -- lsp-set-type ln localnet \ ++ -- lsp-set-options ln network_name=phys \ ++ -- lsp-add ls lsp \ ++ -- lsp-set-addresses lsp "00:00:00:00:00:02 10.0.0.2" ++ ++dnl First bind the localport. ++check ovs-vsctl add-port br-int vif1 \ ++ -- set Interface vif1 external-ids:iface-id=lp ++check ovn-nbctl --wait=hv sync ++ ++dnl Then bind the regular vif. ++check ovs-vsctl add-port br-int vif2 \ ++ -- set Interface vif2 external-ids:iface-id=lsp \ ++ options:tx_pcap=hv1/vif2-tx.pcap \ ++ options:rxq_pcap=hv1/vif2-rx.pcap ++ ++wait_for_ports_up lsp ++check ovn-nbctl --wait=hv sync ++ ++dnl Wait for at least two gARPs from lsp (10.0.0.2). ++lsp_garp=ffffffffffff000000000002080600010800060400010000000000020a0000020000000000000a000002 ++OVS_WAIT_UNTIL([ ++ garps=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | grep ${lsp_garp} -c` ++ test $garps -ge 2 ++]) ++ ++dnl At this point it's safe to assume that ovn-controller skipped sending gARP ++dnl for the localport. Check that there are no other packets than the gARPs ++dnl for the regular vif. ++AT_CHECK([ ++ pkts=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | grep -v ${lsp_garp} -c` ++ test 0 -eq $pkts ++]) ++ ++OVN_CLEANUP([hv1]) ++AT_CLEANUP ++ + AT_SETUP([ovn -- 1 LR with HA distributed router gateway port]) + ovn_start + +@@ -16647,56 +16700,67 @@ ovs-vsctl -- add-port br-int hv2-vif2 -- \ + + ovn-nbctl ls-add sw0 + +-ovn-nbctl lsp-add sw0 sw0-vir +-ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10" +-ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10" +-ovn-nbctl lsp-set-type sw0-vir virtual +-ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10 +-ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3 ++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,sw0-p2,sw0-p3 + +-ovn-nbctl lsp-add sw0 sw0-p1 +-ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3" +-ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3 10.0.0.10" ++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" ++check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3 10.0.0.10" + +-ovn-nbctl lsp-add sw0 sw0-p2 +-ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4" +-ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4 10.0.0.10" ++check ovn-nbctl lsp-add sw0 sw0-p2 ++check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4" ++check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4 10.0.0.10" + +-ovn-nbctl lsp-add sw0 sw0-p3 +-ovn-nbctl lsp-set-addresses sw0-p3 "50:54:00:00:00:05 10.0.0.5" +-ovn-nbctl lsp-set-port-security sw0-p3 "50:54:00:00:00:05 10.0.0.5 10.0.0.10" ++check ovn-nbctl lsp-add sw0 sw0-p3 ++check ovn-nbctl lsp-set-addresses sw0-p3 "50:54:00:00:00:05 10.0.0.5" ++check ovn-nbctl lsp-set-port-security sw0-p3 "50:54:00:00:00:05 10.0.0.5 10.0.0.10" + + # Create the second logical switch with one port +-ovn-nbctl ls-add sw1 +-ovn-nbctl lsp-add sw1 sw1-p1 +-ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3" +-ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3" ++check ovn-nbctl ls-add sw1 ++check ovn-nbctl lsp-add sw1 sw1-p1 ++check ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3" ++check ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3" + + # Create a logical router and attach both logical switches +-ovn-nbctl lr-add lr0 +-ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 +-ovn-nbctl lsp-add sw0 sw0-lr0 +-ovn-nbctl lsp-set-type sw0-lr0 router +-ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01 +-ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 ++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 ++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 + +-ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 +-ovn-nbctl lsp-add sw1 sw1-lr0 +-ovn-nbctl lsp-set-type sw1-lr0 router +-ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02 +-ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1 ++check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 ++check ovn-nbctl lsp-add sw1 sw1-lr0 ++check ovn-nbctl lsp-set-type sw1-lr0 router ++check ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02 ++check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1 + +-OVN_POPULATE_ARP ++# Add an ACL that matches on sw0-vir being bound locally. ++check ovn-nbctl acl-add sw0 to-lport 1000 'is_chassis_resident("sw0-vir") && ip' allow + +-# Delete sw0-vir and add again. +-ovn-nbctl lsp-del sw0-vir ++check ovn-nbctl ls-add public ++check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24 ++check ovn-nbctl lsp-add public public-lr0 ++check ovn-nbctl lsp-set-type public-lr0 router ++check ovn-nbctl lsp-set-addresses public-lr0 router ++check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public + +-ovn-nbctl lsp-add sw0 sw0-vir +-ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10" +-ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10" +-ovn-nbctl lsp-set-type sw0-vir virtual +-ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10 +-ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3 ++# localnet port ++check ovn-nbctl lsp-add public ln-public ++check ovn-nbctl lsp-set-type ln-public localnet ++check ovn-nbctl lsp-set-addresses ln-public unknown ++check ovn-nbctl lsp-set-options ln-public network_name=public ++ ++# schedule the gw router port to a chassis. Change the name of the chassis ++check ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20 ++ ++check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.168.0.50 10.0.0.10 sw0-vir 10:54:00:00:00:10 ++ ++OVN_POPULATE_ARP + + wait_for_ports_up + ovn-nbctl --wait=hv sync +@@ -16746,6 +16810,30 @@ ovs-vsctl del-port hv1-vif3 + AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding \ + logical_port=sw0-vir) = x], [0], []) + ++check_virtual_offlows_present() { ++ hv=$1 ++ ++ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | grep "priority=2000"], [0], [dnl ++ table=45, priority=2000,ip,metadata=0x1 actions=resubmit(,46) ++ table=45, priority=2000,ipv6,metadata=0x1 actions=resubmit(,46) ++]) ++ ++ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \ ++ grep "priority=92" | grep 172.168.0.50], [0], [dnl ++ table=11, priority=92,arp,reg14=0x3,metadata=0x3,arp_tpa=172.168.0.50,arp_op=1 actions=move:NXM_OF_ETH_SRC[[]]->NXM_OF_ETH_DST[[]],mod_dl_src:10:54:00:00:00:10,load:0x2->NXM_OF_ARP_OP[[]],move:NXM_NX_ARP_SHA[[]]->NXM_NX_ARP_THA[[]],load:0x105400000010->NXM_NX_ARP_SHA[[]],move:NXM_OF_ARP_SPA[[]]->NXM_OF_ARP_TPA[[]],load:0xaca80032->NXM_OF_ARP_SPA[[]],move:NXM_NX_REG14[[]]->NXM_NX_REG15[[]],load:0x1->NXM_NX_REG10[[0]],resubmit(,37) ++]) ++} ++ ++check_virtual_offlows_not_present() { ++ hv=$1 ++ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | grep "priority=2000"], [1], [dnl ++]) ++ ++ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \ ++ grep "priority=92" | grep 172.168.0.50], [1], [dnl ++]) ++} ++ + # 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 +@@ -16767,6 +16855,13 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows2 | grep "reg0 == 10.0.0.10" | sed 's/ + table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;) + ]) + ++# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and ++# arp responder flow in lr0 pipeline. ++check_virtual_offlows_present hv1 ++ ++# hv2 should not have the above flows. ++check_virtual_offlows_not_present hv2 ++ + # Forcibly clear virtual_parent. ovn-controller should release the binding + # gracefully. + pb_uuid=$(ovn-sbctl --bare --columns _uuid find port_binding logical_port=sw0-vir) +@@ -16777,6 +16872,13 @@ logical_port=sw0-vir) = x]) + + wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir + ++check ovn-nbctl --wait=hv sync ++# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir. ++check_virtual_offlows_not_present hv1 ++ ++# hv2 should not have the flow for ACL. ++check_virtual_offlows_not_present hv2 ++ + # From sw0-p0 resend GARP for 10.0.0.10. hv1 should reclaim sw0-vir + # and sw0-p1 should be its virtual_parent. + send_garp 1 1 $eth_src $eth_dst $spa $tpa +@@ -16789,6 +16891,58 @@ logical_port=sw0-vir) = xsw0-p1]) + + wait_for_ports_up sw0-vir + ++check ovn-nbctl --wait=hv sync ++# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and ++# arp responder flow in lr0 pipeline. ++check_virtual_offlows_present hv1 ++ ++# hv2 should not have the above flows. ++check_virtual_offlows_not_present hv2 ++ ++# Release sw0-p1. ++as hv1 ovs-vsctl set interface hv1-vif1 external-ids:iface-id=sw0-px ++wait_column "false" nb:Logical_Switch_Port up name=sw0-p1 ++wait_column "false" nb:Logical_Switch_Port up name=sw0-vir ++ ++check ovn-nbctl --wait=hv sync ++# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir and ++# arp responder flow in lr0 pipeline. ++check_virtual_offlows_not_present hv1 ++ ++# hv2 should not have the above flows. ++check_virtual_offlows_not_present hv2 ++ ++# Claim sw0-p1 again. ++as hv1 ovs-vsctl set interface hv1-vif1 external-ids:iface-id=sw0-p1 ++wait_for_ports_up sw0-p1 ++ ++# hv1 should not have the flow for the ACL with is_chassis_redirect check for sw0-vir and ++# arp responder flow in lr0 pipeline. ++check_virtual_offlows_not_present hv1 ++ ++# hv2 should not have the above flows. ++check_virtual_offlows_not_present hv2 ++ ++# 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 ++ ++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 ++ ++# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and ++# arp responder flow in lr0 pipeline. ++check_virtual_offlows_present hv1 ++ ++# hv2 should not have the above flows. ++check_virtual_offlows_not_present hv2 ++ + # From sw0-p3 send GARP for 10.0.0.10. hv1 should claim sw0-vir + # and sw0-p3 should be its virtual_parent. + eth_src=505400000005 +@@ -16806,8 +16960,8 @@ logical_port=sw0-vir) = xsw0-p3]) + wait_for_ports_up sw0-vir + + # There should be an arp resolve flow to resolve the virtual_ip with the +-# sw0-p2's MAC. +-sleep 1 ++# sw0-p3's MAC. ++check ovn-nbctl --wait=hv sync + ovn-sbctl dump-flows lr0 > lr0-flows3 + AT_CAPTURE_FILE([lr0-flows3]) + cp ovn-sb/ovn-sb.db lr0-flows3.db +@@ -16815,6 +16969,13 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows3 | grep "reg0 == 10.0.0.10" | sed 's + table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:05; next;) + ]) + ++# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and ++# arp responder flow in lr0 pipeline. ++check_virtual_offlows_present hv1 ++ ++# hv2 should not have the above flows. ++check_virtual_offlows_not_present hv2 ++ + # send the garp from sw0-p2 (in hv2). hv2 should claim sw0-vir + # and sw0-p2 shpuld be its virtual_parent. + eth_src=505400000004 +@@ -16832,14 +16993,21 @@ logical_port=sw0-vir) = xsw0-p2]) + wait_for_ports_up sw0-vir + + # There should be an arp resolve flow to resolve the virtual_ip with the +-# sw0-p3's MAC. +-sleep 1 ++# sw0-p2's MAC. ++check ovn-nbctl --wait=hv sync + ovn-sbctl dump-flows lr0 > lr0-flows4 + AT_CAPTURE_FILE([lr0-flows4]) + AT_CHECK([grep lr_in_arp_resolve lr0-flows4 | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl + table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;) + ]) + ++# hv2 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and ++# arp responder flow in lr0 pipeline. ++check_virtual_offlows_present hv2 ++ ++# hv1 should not have the above flows. ++check_virtual_offlows_not_present hv1 ++ + # Now send arp reply from sw0-p1. hv1 should claim sw0-vir + # and sw0-p1 shpuld be its virtual_parent. + eth_src=505400000003 +@@ -16863,6 +17031,14 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows5 | grep "reg0 == 10.0.0.10" | sed 's/ + table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;) + ]) + ++check ovn-nbctl --wait=hv sync ++# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and ++# arp responder flow in lr0 pipeline. ++check_virtual_offlows_present hv1 ++ ++# hv2 should not have the above flows. ++check_virtual_offlows_not_present hv2 ++ + # Delete hv1-vif1 port. hv1 should release sw0-vir + as hv1 ovs-vsctl del-port hv1-vif1 + +@@ -16883,6 +17059,15 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows6 | grep "reg0 == 10.0.0.10" | sed 's/ + table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;) + ]) + ++check ovn-nbctl --wait=hv sync ++# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir and ++# arp responder flow in lr0 pipeline. ++check_virtual_offlows_not_present hv1 ++ ++# hv2 should not have the above flows. ++check_virtual_offlows_not_present hv2 ++ ++ + # Now send arp reply from sw0-p2. hv2 should claim sw0-vir + # and sw0-p2 should be its virtual_parent. + eth_src=505400000004 +@@ -16906,6 +17091,14 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows7 | grep "reg0 == 10.0.0.10" | sed 's/ + table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;) + ]) + ++check ovn-nbctl --wait=hv sync ++# hv2 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and ++# arp responder flow in lr0 pipeline. ++check_virtual_offlows_present hv2 ++ ++# hv1 should not have the above flows. ++check_virtual_offlows_not_present hv1 ++ + # Delete sw0-p2 logical port + ovn-nbctl lsp-del sw0-p2 + +@@ -16933,6 +17126,14 @@ AT_CHECK([grep ls_in_arp_rsp sw0-flows3 | grep bind_vport | sed 's/table=../tabl + table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) + ]) + ++check ovn-nbctl --wait=hv sync ++# hv2 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir and ++# arp responder flow in lr0 pipeline. ++check_virtual_offlows_not_present hv2 ++ ++# hv1 should not have the above flows. ++check_virtual_offlows_not_present hv2 ++ + ovn-nbctl --wait=hv remove logical_switch_port sw0-vir options virtual-parents + ovn-sbctl dump-flows sw0 > sw0-flows4 + AT_CAPTURE_FILE([sw0-flows4]) +@@ -16942,6 +17143,38 @@ ovn-sbctl dump-flows lr0 > lr0-flows8 + AT_CAPTURE_FILE([lr0-flows8]) + AT_CHECK([grep lr_in_arp_resolve lr0-flows8 | grep "reg0 == 10.0.0.10"], [1]) + ++# Delete sw0-vir and add again. ++ovn-nbctl lsp-del sw0-vir ++ ++ovn-nbctl lsp-add sw0 sw0-vir ++ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10" ++ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10" ++ovn-nbctl lsp-set-type sw0-vir virtual ++ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10 ++ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3 ++ ++ovn-nbctl --wait=hv sync ++ ++# Check that logical flows are added for sw0-vir in lsp_in_arp_rsp pipeline ++# with bind_vport action. ++ ++ovn-sbctl dump-flows sw0 > sw0-flows ++AT_CAPTURE_FILE([sw0-flows]) ++ ++AT_CHECK([grep ls_in_arp_rsp sw0-flows | grep bind_vport | sed 's/table=../table=??/' | sort], [0], [dnl ++ table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) ++ table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) ++]) ++ ++ovn-sbctl dump-flows lr0 > lr0-flows ++AT_CAPTURE_FILE([lr0-flows]) ++ ++# Since the sw0-vir is not claimed by any chassis, eth.dst should be set to ++# zero if the ip4.dst is the virtual ip in the router pipeline. ++AT_CHECK([grep lr_in_arp_resolve lr0-flows | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl ++ table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;) ++]) ++ + OVN_CLEANUP([hv1], [hv2]) + AT_CLEANUP + +@@ -24918,3 +25151,633 @@ AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST], [1], [dnl + + OVN_CLEANUP([hv1], [hv2]) + AT_CLEANUP ++ ++AT_SETUP([ovn -- container port changed to normal port and then deleted]) ++ovn_start ++ ++net_add n1 ++ ++sim_add hv1 ++as hv1 ++ovs-vsctl add-br br-phys ++ovn_attach n1 br-phys 192.168.0.1 ++ovs-vsctl -- add-port br-int vm1 ++ ++check ovn-nbctl ls-add ls ++check ovn-nbctl lsp-add ls vm1 ++check ovn-nbctl lsp-add ls vm-cont vm1 1 ++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 ++ ++wait_for_ports_up ++ ++check as hv1 ovn-appctl -t ovn-controller debug/pause ++check ovn-nbctl clear logical_switch_port vm-cont parent_name ++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=foo ++check ovn-nbctl lsp-del vm-cont ++check as hv1 ovn-appctl -t ovn-controller debug/resume ++ ++ovn-nbctl --wait=hv sync ++ ++# Make sure that ovn-controller has not asserted. ++AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) ++ ++wait_column "false" nb:Logical_Switch_Port up name=vm1 ++ ++check ovn-nbctl lsp-add ls vm-cont1 vm1 1 ++check ovn-nbctl lsp-add ls vm-cont2 vm1 2 ++ ++check ovn-nbctl --wait=sb lsp-del vm1 ++ ++check as hv1 ovn-appctl -t ovn-controller debug/pause ++check ovn-nbctl clear logical_switch_port vm-cont1 parent_name ++check ovn-nbctl clear logical_switch_port vm-cont2 parent_name ++ ++check as hv1 ovn-appctl -t ovn-controller debug/resume ++ ++check ovn-nbctl --wait=hv sync ++ ++# Make sure that ovn-controller has not crashed. ++AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) ++ ++check ovn-nbctl lsp-add ls vm1 ++check ovn-nbctl set logical_switch_port vm-cont1 parent_name=vm1 ++check ovn-nbctl --wait=sb set logical_switch_port vm-cont2 parent_name=vm1 ++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 ++ ++wait_for_ports_up ++ ++check as hv1 ovn-appctl -t ovn-controller debug/pause ++check ovn-nbctl --wait=sb lsp-del vm1 ++check ovn-nbctl clear logical_switch_port vm-cont1 parent_name ++check ovn-nbctl --wait=sb clear logical_switch_port vm-cont2 parent_name ++check ovn-nbctl lsp-del vm-cont1 ++check ovn-nbctl --wait=sb lsp-del vm-cont2 ++check as hv1 ovn-appctl -t ovn-controller debug/resume ++ ++check ovn-nbctl --wait=hv sync ++ ++# Make sure that ovn-controller has not crashed. ++AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) ++ ++check ovn-nbctl lsp-add ls vm1 ++check ovn-nbctl lsp-add ls vm-cont1 vm1 1 ++check ovn-nbctl lsp-add ls vm-cont2 vm1 2 ++ ++wait_for_ports_up ++ ++check as hv1 ovn-appctl -t ovn-controller debug/pause ++check ovn-nbctl clear logical_switch_port vm-cont1 parent_name ++check ovn-nbctl --wait=sb clear logical_switch_port vm-cont2 parent_name ++check ovn-nbctl lsp-del vm-cont1 ++check ovn-nbctl lsp-del vm-cont2 ++check as hv1 ovn-appctl -t ovn-controller debug/resume ++ ++check ovn-nbctl --wait=hv sync ++ ++# Make sure that ovn-controller has not crashed. ++AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) ++ ++check ovn-nbctl lsp-add ls vm-cont1 vm1 1 ++check ovn-nbctl lsp-add ls vm-cont2 vm1 2 ++ ++wait_for_ports_up ++ ++check as hv1 ovn-appctl -t ovn-controller debug/pause ++check ovn-nbctl clear logical_switch_port vm-cont1 parent_name ++check ovn-nbctl --wait=sb clear logical_switch_port vm-cont2 parent_name ++ ++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=foo ++ ++check as hv1 ovn-appctl -t ovn-controller debug/resume ++ ++wait_column "false" nb:Logical_Switch_Port up name=vm1 ++wait_column "false" nb:Logical_Switch_Port up name=vm-cont1 ++wait_column "false" nb:Logical_Switch_Port up name=vm-cont2 ++ ++check ovn-nbctl set logical_switch_port vm-cont1 parent_name=vm1 ++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 ++check ovn-nbctl --wait=sb set logical_switch_port vm-cont2 parent_name=vm1 ++ ++wait_for_ports_up ++ ++check as hv1 ovn-appctl -t ovn-controller debug/pause ++check ovn-nbctl clear logical_switch_port vm-cont1 parent_name ++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm-cont1 ++check as hv1 ovn-appctl -t ovn-controller debug/resume ++ ++wait_column "false" nb:Logical_Switch_Port up name=vm1 ++wait_column "true" nb:Logical_Switch_Port up name=vm-cont1 ++wait_column "false" nb:Logical_Switch_Port up name=vm-cont2 ++ ++check ovn-nbctl --wait=sb set logical_switch_port vm-cont2 parent_name=vm-cont1 ++check ovn-nbctl --wait=sb set logical_switch_port vm1 parent_name=vm-cont1 ++ ++wait_for_ports_up ++ ++# Delete vm1, vm-cont1 and vm-cont2 and recreate again. ++check ovn-nbctl lsp-del vm1 ++check ovn-nbctl lsp-del vm-cont1 ++check ovn-nbctl --wait=hv lsp-del vm-cont2 ++ ++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 ++check ovn-nbctl lsp-add ls vm1 ++check ovn-nbctl lsp-add ls vm-cont1 vm1 1 ++check ovn-nbctl lsp-add ls vm-cont2 vm1 2 ++ ++wait_for_ports_up ++ ++# Make vm1 as a child port of some non existent lport - foo. vm1, vm1-cont1 and ++# vm1-cont2 should be released. ++check ovn-nbctl --wait=sb set logical_switch_port vm1 parent_name=bar ++wait_column "false" nb:Logical_Switch_Port up name=vm1 ++wait_column "false" nb:Logical_Switch_Port up name=vm-cont1 ++wait_column "false" nb:Logical_Switch_Port up name=vm-cont2 ++ ++OVN_CLEANUP([hv1]) ++AT_CLEANUP ++ ++AT_SETUP([ovn -- container port changed from one parent to another]) ++ovn_start ++ ++net_add n1 ++ ++sim_add hv1 ++as hv1 ++ovs-vsctl add-br br-phys ++ovn_attach n1 br-phys 192.168.0.1 ++ovs-vsctl -- add-port br-int vm1 -- set interface vm1 ofport-request=1 ++ovs-vsctl -- add-port br-int vm2 -- set interface vm1 ofport-request=2 ++ ++check ovn-nbctl ls-add ls ++check ovn-nbctl lsp-add ls vm1 ++check ovn-nbctl lsp-add ls vm1-cont vm1 1 ++check ovn-nbctl lsp-add ls vm2 ++check ovn-nbctl lsp-add ls vm2-cont vm2 2 ++ ++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 ++check as hv1 ovs-vsctl set Interface vm2 external_ids:iface-id=vm2 ++ ++wait_for_ports_up ++ ++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=1], [0], [dnl ++1 ++]) ++ ++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=2], [0], [dnl ++1 ++]) ++ ++# change the parent of vm1-cont to vm2. ++as hv1 ovn-appctl -t ovn-controller vlog/set dbg ++check ovn-nbctl --wait=sb set logical_switch_port vm1-cont parent_name=vm2 \ ++-- set logical_switch_port vm1-cont tag_request=3 ++ ++wait_for_ports_up ++ ++check ovn-nbctl --wait=hv sync ++ ++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=1], [1], [dnl ++0 ++]) ++ ++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=2], [0], [dnl ++1 ++]) ++ ++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=3], [0], [dnl ++1 ++]) ++ ++OVN_CLEANUP([hv1]) ++AT_CLEANUP ++ ++AT_SETUP([ovn -- container port use-after-free test]) ++ovn_start ++ ++net_add n1 ++ ++sim_add hv1 ++as hv1 ++ovs-vsctl add-br br-phys ++ovn_attach n1 br-phys 192.168.0.1 ++ovs-vsctl -- add-port br-int vm1 ++ ++check ovn-nbctl ls-add ls ++check ovn-nbctl lsp-add ls vm1 ++check ovn-nbctl lsp-add ls vm-cont vm1 1 ++check ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 ++check ovn-nbctl clear logical_switch_port vm-cont parent_name ++check ovs-vsctl set Interface vm1 external_ids:iface-id=foo ++check ovn-nbctl lsp-del vm-cont ++check ovn-nbctl ls-del ls ++check ovn-nbctl ls-add ls ++check ovn-nbctl lsp-add ls vm1 ++check ovn-nbctl lsp-add ls vm-cont vm1 1 ++check ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 ++check as hv1 ovn-appctl -t ovn-controller debug/pause ++check ovn-nbctl clear logical_switch_port vm-cont parent_name ++check ovn-nbctl lsp-del vm-cont ++check as hv1 ovn-appctl -t ovn-controller debug/resume ++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=foo ++ ++ovn-nbctl --wait=hv sync ++ ++# Make sure that ovn-controller has not asserted. ++AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) ++ ++wait_column "false" nb:Logical_Switch_Port up name=vm1 ++ ++OVN_CLEANUP([hv1]) ++AT_CLEANUP ++ ++# Test that OVS.external_ids:iface-id doesn't affect non-VIF port bindings. ++AT_SETUP([ovn -- Non-VIF ports incremental processing]) ++ovn_start ++ ++net_add n1 ++sim_add hv1 ++as hv1 ++ovs-vsctl add-br br-phys ++ovn_attach n1 br-phys 192.168.0.10 ++ ++check ovn-nbctl ls-add ls1 -- lsp-add ls1 lsp1 ++ ++as hv1 ++check ovs-vsctl \ ++ -- add-port br-int vif1 \ ++ -- set Interface vif1 external_ids:iface-id=lsp1 ++ ++# ovn-controller should bind the interface. ++wait_for_ports_up ++hv_uuid=$(fetch_column Chassis _uuid name=hv1) ++check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1 ++ ++# Change the port type to router, ovn-controller should release it. ++check ovn-nbctl --wait=hv lsp-set-type lsp1 router ++check_column "" Port_Binding chassis logical_port=lsp1 ++ ++# Clear port type, ovn-controller should rebind it. ++check ovn-nbctl --wait=hv lsp-set-type lsp1 '' ++check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1 ++ ++# Change the port type to localnet, ovn-controller should release it. ++check ovn-nbctl --wait=hv lsp-set-type lsp1 localnet ++check_column "" Port_Binding chassis logical_port=lsp1 ++ ++# Clear port type, ovn-controller should rebind it. ++check ovn-nbctl --wait=hv lsp-set-type lsp1 '' ++check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1 ++ ++# Change the port type to localport, ovn-controller should release it. ++check ovn-nbctl --wait=hv lsp-set-type lsp1 localport ++check_column "" Port_Binding chassis logical_port=lsp1 ++ ++# Clear port type, ovn-controller should rebind it. ++check ovn-nbctl --wait=hv lsp-set-type lsp1 '' ++check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1 ++ ++# Change the port type to localnet and then delete it. ++# ovn-controller should handle this properly. ++check as hv1 ovn-appctl -t ovn-controller debug/pause ++check ovn-nbctl --wait=sb lsp-set-type lsp1 localport ++check ovn-nbctl --wait=sb lsp-del lsp1 ++check as hv1 ovn-appctl -t ovn-controller debug/resume ++ ++check ovn-nbctl --wait=hv sync ++ ++# Make sure that ovn-controller has not asserted. ++AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) ++ ++check ovn-nbctl lsp-add ls1 lsp1 ++wait_for_ports_up ++ ++# Change the port type to virtual and then delete it. ++# ovn-controller should handle this properly. ++check as hv1 ovn-appctl -t ovn-controller debug/pause ++check ovn-nbctl --wait=sb lsp-set-type lsp1 virtual ++check ovn-nbctl --wait=sb lsp-del lsp1 ++check as hv1 ovn-appctl -t ovn-controller debug/resume ++ ++check ovn-nbctl --wait=hv sync ++ ++# Make sure that ovn-controller has not asserted. ++AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) ++ ++OVN_CLEANUP([hv1]) ++AT_CLEANUP ++ ++# Tests that ovn-controller creates local bindings correctly by running ++# ovn-appctl -t ovn-controller debug/dump-local-bindings. ++# Ideally this test case should have been a unit test case. ++AT_SETUP([ovn -- ovn-controller local bindings]) ++ovn_start ++ ++net_add n1 ++ ++sim_add hv1 ++as hv1 ++ovs-vsctl add-br br-phys ++ovn_attach n1 br-phys 192.168.0.1 ++ovs-vsctl -- add-port br-int hv1-vm1 ++ ++sim_add hv2 ++as hv2 ++ovs-vsctl add-br br-phys ++ovn_attach n1 br-phys 192.168.0.2 ++ovs-vsctl -- add-port br-int hv2-vm1 ++ ++check ovn-nbctl ls-add sw0 ++check ovn-nbctl lsp-add sw0 sw0p1 ++check ovn-nbctl lsp-add sw0 sw0p2 ++ ++check as hv1 ovs-vsctl set interface hv1-vm1 external_ids:iface-id=sw0p1 ++check as hv2 ovs-vsctl set interface hv2-vm1 external_ids:iface-id=sw0p2 ++ ++wait_for_ports_up ++ ++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] ++primary lport : [[sw0p1]] ++---------------------------------------- ++]) ++ ++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]] ++primary lport : [[sw0p2]] ++---------------------------------------- ++]) ++ ++# Create an ovs interface in hv1 ++check as hv1 ovs-vsctl add-port br-int hv1-vm2 -- set interface hv1-vm2 external_ids:iface-id=sw1p1 ++check ovn-nbctl --wait=hv sync ++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] ++primary lport : [[sw0p1]] ++---------------------------------------- ++name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[0]] ++---------------------------------------- ++]) ++ ++# Create lport sw1p1 ++check ovn-nbctl ls-add sw1 -- lsp-add sw1 sw1p1 ++ ++wait_for_ports_up ++ ++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] ++primary lport : [[sw0p1]] ++---------------------------------------- ++name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] ++primary lport : [[sw1p1]] ++---------------------------------------- ++]) ++ ++# Swap sw0p1 and sw0p2. ++check as hv1 ovs-vsctl set interface hv1-vm1 external_ids:iface-id=sw0p2 ++check as hv2 ovs-vsctl set interface hv2-vm1 external_ids:iface-id=sw0p1 ++ ++check ovn-nbctl --wait=hv sync ++ ++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[sw0p2]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] ++primary lport : [[sw0p2]] ++---------------------------------------- ++name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] ++primary lport : [[sw1p1]] ++---------------------------------------- ++]) ++ ++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[sw0p1]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]] ++primary lport : [[sw0p1]] ++---------------------------------------- ++]) ++ ++# Create child port for sw0p1 ++check ovn-nbctl --wait=hv lsp-add sw0 sw0p1-c1 sw0p1 1 ++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] ++primary lport : [[sw0p1]] ++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p2]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] ++primary lport : [[sw0p2]] ++---------------------------------------- ++name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] ++primary lport : [[sw1p1]] ++---------------------------------------- ++]) ++ ++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[sw0p1]], OVS interface name : [[hv2-vm1]], num binding lports : [[2]] ++primary lport : [[sw0p1]] ++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] ++---------------------------------------- ++]) ++ ++# Create another child port for sw0p1 ++check ovn-nbctl --wait=hv lsp-add sw0 sw0p1-c2 sw0p1 2 ++ ++wait_for_ports_up ++ ++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[3]] ++primary lport : [[sw0p1]] ++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] ++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p2]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] ++primary lport : [[sw0p2]] ++---------------------------------------- ++name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] ++primary lport : [[sw1p1]] ++---------------------------------------- ++]) ++ ++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[sw0p1]], OVS interface name : [[hv2-vm1]], num binding lports : [[3]] ++primary lport : [[sw0p1]] ++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] ++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] ++---------------------------------------- ++]) ++ ++# Swap sw0p1 and sw0p2 again. ++check as hv1 ovs-vsctl set interface hv1-vm1 external_ids:iface-id=sw0p1 ++check as hv2 ovs-vsctl set interface hv2-vm1 external_ids:iface-id=sw0p2 ++ ++check ovn-nbctl --wait=hv sync ++ ++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[3]] ++primary lport : [[sw0p1]] ++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] ++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] ++primary lport : [[sw1p1]] ++---------------------------------------- ++]) ++ ++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[3]] ++primary lport : [[sw0p1]] ++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] ++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]] ++primary lport : [[sw0p2]] ++---------------------------------------- ++]) ++ ++# Make sw0p1 as child port of non existent lport - foo ++check ovn-nbctl --wait=hv set logical_switch_port sw0p1 parent_name=foo ++ ++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] ++no primary lport ++child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[2]] ++no primary lport ++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] ++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] ++primary lport : [[sw1p1]] ++---------------------------------------- ++]) ++ ++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] ++no primary lport ++child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] ++no primary lport ++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] ++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]] ++primary lport : [[sw0p2]] ++---------------------------------------- ++]) ++ ++# Change the lport type of sw0p2 to different types and make sure that ++# local bindings are correct. ++ ++hv2_uuid=$(fetch_column Chassis _uuid name=hv2) ++check_column "$hv2_uuid" Port_Binding chassis logical_port=sw0p2 ++ ++# Change the port type to router, ovn-controller should release it. ++check ovn-nbctl --wait=hv lsp-set-type sw0p2 router ++check_column "" Port_Binding chassis logical_port=sw0p2 ++ ++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] ++no primary lport ++child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] ++no primary lport ++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] ++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]] ++---------------------------------------- ++]) ++ ++# change the port type to external from router. ++check ovn-nbctl --wait=hv lsp-set-type sw0p2 external ++check_column "" Port_Binding chassis logical_port=sw0p2 ++ ++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] ++no primary lport ++child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] ++no primary lport ++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] ++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]] ++---------------------------------------- ++]) ++ ++# change the port type to localnet from external. ++check ovn-nbctl --wait=hv lsp-set-type sw0p2 localnet ++check_column "" Port_Binding chassis logical_port=sw0p2 ++ ++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] ++no primary lport ++child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] ++no primary lport ++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] ++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]] ++---------------------------------------- ++]) ++ ++# change the port type to localport from localnet. ++check ovn-nbctl --wait=hv lsp-set-type sw0p2 localnet ++check_column "" Port_Binding chassis logical_port=sw0p2 ++ ++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] ++no primary lport ++child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] ++no primary lport ++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] ++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]] ++---------------------------------------- ++]) ++ ++# change the port type back to vif. ++check ovn-nbctl --wait=hv lsp-set-type sw0p2 "" ++wait_column "$hv2_uuid" Port_Binding chassis logical_port=sw0p2 ++ ++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl ++Local bindings: ++name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] ++no primary lport ++child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] ++no primary lport ++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] ++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] ++---------------------------------------- ++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]] ++primary lport : [[sw0p2]] ++---------------------------------------- ++]) ++ ++OVN_CLEANUP([hv1], [hv2]) ++AT_CLEANUP +diff --git a/utilities/ovndb-servers.ocf b/utilities/ovndb-servers.ocf +index 7351c7d64..eba9c97a1 100755 +--- a/utilities/ovndb-servers.ocf ++++ b/utilities/ovndb-servers.ocf +@@ -259,6 +259,9 @@ ovsdb_server_notify() { + ovn-nbctl -- --id=@conn_uuid create Connection \ + target="p${NB_MASTER_PROTO}\:${NB_MASTER_PORT}\:${LISTEN_ON_IP}" \ + inactivity_probe=$INACTIVE_PROBE -- set NB_Global . connections=@conn_uuid ++ else ++ CONN_UID=$(sed -e 's/^\[//' -e 's/\]$//' <<< ${conn}) ++ ovn-nbctl set connection "${CONN_UID}" target="p${NB_MASTER_PROTO}\:${NB_MASTER_PORT}\:${LISTEN_ON_IP}" + fi + + conn=`ovn-sbctl get SB_global . connections` +@@ -267,6 +270,9 @@ inactivity_probe=$INACTIVE_PROBE -- set NB_Global . connections=@conn_uuid + ovn-sbctl -- --id=@conn_uuid create Connection \ + target="p${SB_MASTER_PROTO}\:${SB_MASTER_PORT}\:${LISTEN_ON_IP}" \ + inactivity_probe=$INACTIVE_PROBE -- set SB_Global . connections=@conn_uuid ++ else ++ CONN_UID=$(sed -e 's/^\[//' -e 's/\]$//' <<< ${conn}) ++ ovn-sbctl set connection "${CONN_UID}" target="p${SB_MASTER_PROTO}\:${SB_MASTER_PORT}\:${LISTEN_ON_IP}" + fi + + else diff --git a/SPECS/ovn-2021.spec b/SPECS/ovn-2021.spec new file mode 100644 index 0000000..b85781f --- /dev/null +++ b/SPECS/ovn-2021.spec @@ -0,0 +1,618 @@ +# 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 ovn-2021 + +# 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: 21.03.0 +Release: 21%{?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 + +# Always pull an upstream release, since this is what we rebase to. +Source: https://github.com/ovn-org/ovn/archive/v%{version}.tar.gz#/ovn-%{version}.tar.gz + +%define ovscommit ac85cdb38c1f33e7952bc4c0347d6c7873fb56a1 +%define ovsshortcommit ac85cdb + +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: ovn-%{version}.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} > 7 || 0%{?fedora} +BuildRequires: python3-pyOpenSSL +%endif + +%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 +%if 0%{?commit0:1} +%autosetup -n ovn-%{commit0} -a 10 -p 1 +%else +%autosetup -n ovn-%{version} -a 10 -p 1 +%endif + +%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} -k ovn'; 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-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 +* Wed Apr 14 2021 Numan Siddique - 21.03.0-21 +- controller: Fix virtual lport I-P handling. (#1947823) + [Gerrit: 0938c49138dac280bbc59148fe87dc0debed6f62] + [Upstream: 1ad0a974b55dc6f31f7ea940e3b7d63368babb04] + +* Tue Apr 13 2021 Dumitru Ceara - 21.03.0-20 +- northd: Restore flows that recirculate packets in the router DNAT zone. + [Gerrit: 4f5d3099d94c4860737546c4c1f6561f15dc7519] + [Upstream: 82b4c619dd6c772a50d5403bf6d40aa4b4f7e38d] + +* Tue Apr 13 2021 Numan Siddique - 21.03.0-19 +- Merge "binding: Fix the crashes seen when port binding type changes." into ovn-2021 + [Gerrit: 5a15f57371ce318a382c0e3aa262e5dab790e168] + [Upstream: N/A] + +* Tue Apr 13 2021 Lorenzo Bianconi - 21.03.0-18 +- Merge "northd: introduce per-lb lb_skip_snat option" into ovn-2021 + [Gerrit: 91e869a442511d649b123d42e69b789f5d3ee96e] + [Upstream: N/A] + +* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-17 +- northd: introduce lrouter_check_nat_entry routine + [Gerrit: 0b3ca120ea30a2b1d34d5b55e9b7b953757da4dd] + [Upstream: e02cd3d2001db87b92bd139eab533e69e0d48aee] + +* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-16 +- northd: introduce build_lrouter_ingress_flow routine + [Gerrit: db27342ff9355f45021013ff3aaf2ebafe71c47a] + [Upstream: 0d16a8b64c5035529cbbbf245384618711024ae4] + +* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-15 +- northd: introduce build_lrouter_out_snat_flow routine + [Gerrit: 37b0ba144b999a089cd439023f90980651400616] + [Upstream: 5e8fadf69161bc7e56b8f9f57124e5083b496b83] + +* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-14 +- northd: introduce build_lrouter_out_undnat_flow routine + [Gerrit: 68226a2a7dd698bb0097a87c2f2367c261f5f234] + [Upstream: d8edf46f9e40791954d6bfc0231064e6e09252db] + +* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-13 +- northd: introduce build_lrouter_in_dnat_flow routine + [Gerrit: a5189c81e017ef54c5612072c025fb0b2b24f836] + [Upstream: 225426081f8533e3d4df022b392105028f8bb37c] + +* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-12 +- northd: introduce build_lrouter_in_unsnat_flow routine + [Gerrit: 083b610a6cabac7bb5136e72a15e35ae8a95b6fe] + [Upstream: fa91da7c9979d7b21b7a2a5557705c238cde97a0] + +* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-11 +- northd: introduce build_lrouter_lb_flows routine + [Gerrit: d737b0572331da6e54b7dfa6a941be48035e061f] + [Upstream: 949e4319904938c1d83df2557c37b4bdfa6cbf25] + +* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-10 +- northd: reduce indentation in build_lrouter_nat_defrag_and_lb + [Gerrit: 2de341869c169b807c8ae0abb413d01e381e698b] + [Upstream: 3ba84a110fd969d2c017070b5047f6df1129ac48] + +* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-9 +- controller: introduce stats counters for ovn-controller incremental processing + [Gerrit: eee1993bbf9812adbb3717a2868b87cb01124665] + [Upstream: 0ddb8b2c979c1102d206b4f855eb5fbe1566768e] + +* Mon Mar 29 2021 Mark Michelson - 21.03.0-8 +- Add distgit syncing features. + [Gerrit: fccf9eb76e6e89e51788a3c3decf02b3b7cf621d] + [Upstream: N/A] + +* Thu Mar 25 2021 Michele Baldessari - 21.03.0-7 +- Fix connection string in case of changes in the ovndb-servers.ocf RA + [Gerrit: aab170f85e7e271d199f62ca3f1d050531f124bf] + [Upstream: 7f8bb3f2f77567d8fb30657ad5c3a9408692d6b5] + +* Thu Mar 25 2021 Daniel Alvarez Sanchez - 21.03.0-6 +- pinctrl: Don't send gARPs for localports (#1939470) + [Gerrit: a49a1c229790e896d391fcc0a6ed07fbf977963f] + [Upstream: 578238b36073256c524a4c2b6ed7521f73aa0019] + +* Mon Mar 15 2021 Ilya Maximets - 21.03.0-5 +- ci: Fix handling of python packages. + [Gerrit: da028c72bdc7742b3065d1df95a3789fbc16b27a] + [Upstream: 338a6ddb5ea1c89b48c484b0448a216a82225adc] + +* Fri Mar 12 2021 Mark Michelson - 21.03.0-4 +- Prepare for 21.03.1 + [Gerrit: 79d8c9d594f8cda5023d3e1fefbaf53e109de89b] + [Upstream: N/A] + diff --git a/SPECS/ovn2.13.spec b/SPECS/ovn2.13.spec deleted file mode 100644 index 1b900e5..0000000 --- a/SPECS/ovn2.13.spec +++ /dev/null @@ -1,856 +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 ovn2.13 - -# 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: 20.12.0 -Release: 85%{?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 - -# Always pull an upstream release, since this is what we rebase to. -Source: https://github.com/ovn-org/ovn/archive/v%{version}.tar.gz#/ovn-%{version}.tar.gz - -%define ovscommit ac09cbfcb70ac6f443f039d5934448bd80f74493 -%define ovsshortcommit ac09cbf - -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: ovn-%{version}.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} > 7 || 0%{?fedora} -BuildRequires: python3-pyOpenSSL -%endif - -%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 -%if 0%{?commit0:1} -%autosetup -n ovn-%{commit0} -a 10 -p 1 -%else -%autosetup -n ovn-%{version} -a 10 -p 1 -%endif - -%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} -k ovn'; 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-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 Mar 16 2021 Mark Michelson - 20.12.0-85 -- ovs: Bump submodule version to latest ovsdb-cs changes. - [a79aa8ecc00450ab9c672dbe8add9a8a231186ab] - -* Tue Mar 16 2021 Mark Michelson - 20.12.0-84 -- Add missing patch release number on the end of OVN versions. - [49444fe569740deb10be87418ab4f9ed1d0d5b4e] - -* Fri Mar 12 2021 Numan Siddique - 20.12.0-83 -- northd: Fix ha_chassis_group txn error for external ports. (#1927369) - [f3e58ca5a997ab7fad0c44c0c5968e4ee53f85d5] - -* Wed Mar 10 2021 Gerrit Code Review - 20.12.0-82 -- Merge "Add distgit syncing features." into ovn2.13 - [fdc3e0c5b51295a3c66b469476878128bc013280] - -* Sat Mar 06 2021 Numan Siddique - 20.12.0-81 -- binding: Fix potential NULL dereference of lbinding. - [de6cb3a9f2d8b323d759ab70a81acac2f73d2d77] - -* Sat Mar 06 2021 Numan Siddique - 20.12.0-80 -- northd: Fix the missing force_snat_for_lb flows when router_ip is configured. - [ec4f7228c1b828974ec2909bd108af6a94c3ebc9] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-79 -- northd: Avoid matching on ct.dnat flags for load balancers. - [2baa1b74fb8e93e158d4800d9e42e24b70b3970a] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-78 -- lflow: Avoid matching on conntrack original tuple if possible. - [35a6507db36c23724bc319d56dc6b05dd6d60a99] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-77 -- Properly handle hairpin traffic for VIPs with shared backends. (#1931599) - [811c575effb27cbc416986fafd2e8c815daa4ac6] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-76 -- mac-learn: Fix build due to missing newline at EOF. - [492f5b3ce7c76af4197ded8540913ea52d272136] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-75 -- northd: Cleanup stale FDB entries. - [33fe76837b1da9f8f9f1306a78b74d72a5449537] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-74 -- Fix the failing test case - ovn -- ACL skip hints for stateless config. - [f69f2d79d50efce77831f04464b47a3a58cbc3f4] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-73 -- northd: MAC learning: Add logical flows for fdb. - [da4e67e3ebaf75701f7018f893d2b3285a5da394] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-72 -- controller: MAC learning: Add OF rules for the FDB entries. - [54ae01ced6d791f87fd9ca8ef6a8d6680ce7373f] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-71 -- MAC learning: Add new actions - put_fdb, get_fdb and lookup_fdb. - [cfa19b0905752503056e1e8e4246a629da1d8fbd] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-70 -- MAC learning: Add a new FDB table in southbound db. - [853c8bfd101109fce7b7422486ddaff4e2e6aa03] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-69 -- controller: Split mac learning code to a separate file. - [1feebd8577989a0c01bc27d4ee5a8b9eb323ac47] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-68 -- Add sctp_abort logical flow action. - [180afce292e2c46966ba56cce128f5b3842133c5] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-67 -- Implement SCTP-specific reject() action. - [35afc86abf71ae557acf2fb7030ca8eedf214449] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-66 -- tests: Improve debuggability of tests. - [5fdd36b0afc25a20bde632278878bbfa7fcbc7ce] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-65 -- tests: Add more checking to "3 HVs, 1 LS, 3 lports/HV" test. - [19f0bbfbb4f3b5c0a382154c60b6a53f43cff7a6] - -* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-64 -- tests: Eliminate most "sleep" calls. - [1883c14bb0eb136f2339196218e5c107cf0f79aa] - -* Tue Feb 23 2021 Numan Siddique - 20.12.0-63 -- ofctrl: Fix the assert seen when flood removing flows with conj actions. (#1929978) - [16476ae504d692ec0a7440c27037d155a3f76445] - -* Mon Feb 22 2021 Numan Siddique - 20.12.0-62 -- ofctrl: Do not link a desired flow twice. - [d17a9c36dfa71b04ebea82e4720279b35d42677f] - -* Mon Feb 22 2021 Numan Siddique - 20.12.0-61 -- binding: Fix potential crash when binding_seqno_run is skipped. (#1930030) - [dae3f159a10a3499ef001fddd52e6cd1949927be] - -* Mon Feb 22 2021 Numan Siddique - 20.12.0-60 -- northd: Provide the Gateway router option 'lb_force_snat_ip' to take router port ips. - [e6a52543078d2d618b72151608b9c61326debe05] - -* Wed Feb 17 2021 Gerrit Code Review - 20.12.0-59 -- Merge "Fix submodule build when using build directory." into ovn2.13 - [70de1e7f6d98dc45ceb40f5d91627767e3776ae2] - -* Wed Feb 17 2021 Gerrit Code Review - 20.12.0-58 -- Merge "Add ovs submodule." into ovn2.13 - [ae816ae0378675ea180879548e41f397ffe61c23] - -* Wed Feb 17 2021 Numan Siddique - 20.12.0-57 -- ofctrl: Fix the assert seen when flood removing flows. (#1928012) - [6cce5c6380d33d8ae0795c10737d637f29680e32] - -* Tue Feb 16 2021 Gerrit Code Review - 20.12.0-56 -- Merge "Makefile.am: Fix broken distribution and echo -n checks." into ovn2.13 - [bd7708136650c8f2abb6c5ac005b3141406ec1b5] - -* Sat Feb 13 2021 Numan Siddique - 20.12.0-55 -- controller: Fix toggling ct zone ids. (#1903210) - [48dfb211ef1ecbcee39df4647d905a924b95fcb3] - -* Thu Feb 11 2021 Lorenzo Bianconi - 20.12.0-54 -- ovn-nbctl: do not allow duplicated ECMP routes - [1a7f0f5bd5c23ff5d2583631288001f9bd1e5763] - -* Wed Feb 10 2021 Mark Michelson - 20.12.0-53 -- northd: Skip matching on ct flags for stateless configurations. (#1927211) - [e58c182eaffa79cfdee407bf06531007bf1e3c63] - -* Tue Feb 09 2021 Dumitru Ceara - 20.12.0-52 -- lflow: Use learn() action to generate LB hairpin reply flows. - [0ea619c80146bffd4fef7ac1a8a2aa07f9003eb0] - -* Tue Feb 09 2021 Dumitru Ceara - 20.12.0-51 -- Support configuring Load Balancer hairpin source IP. - [8788ac191a4e0689f0287695c181fe1a781b0d31] - -* Mon Feb 08 2021 Dumitru Ceara - 20.12.0-50 -- tests: Fix Port_Binding up test. - [4e143c1e58b18adf6914ec783ee4503a63dbf3a8] - -* Mon Feb 08 2021 Dumitru Ceara - 20.12.0-49 -- northd: Allow backwards compatibility for Logical_Switch_Port.up. - [07b0f0468faeeb1e149dcc3e4926a54cbb9bb367] - -* Mon Feb 08 2021 Dumitru Ceara - 20.12.0-48 -- binding: Set Port_Binding.up only if supported. - [3de7959b9018f53abd06320bc7f1a43ec216db7e] - -* Mon Feb 08 2021 Dumitru Ceara - 20.12.0-47 -- binding: Correctly set Port_Binding.up for container/virtual ports. - [36a57e7a388277d1e45f0cadd8e2601490a76b2d] - -* Thu Feb 04 2021 Numan Siddique - 20.12.0-46 -- Change the gitreview branch to ovn2.13 - [e7d6708ff347a7e02b16b7205b92d052e5681a37] - -* Wed Feb 03 2021 Lorenzo Bianconi - 20.12.0-45 -- ovn-nbctl: add --bfd option to lr-route-add - [97b58dde0f92fc83165a6db816456073f5ddf727] - -* Fri Jan 29 2021 Dumitru Ceara - 20.12.0-44 -- binding: Set Logical_Switch_Port.up when all OVS flows are installed. - [3719a1add73b860c50d85fad0b270c1b69fb9147] - -* Fri Jan 29 2021 Dumitru Ceara - 20.12.0-43 -- controller: Implement a generic barrier based on ofctrl cur_cfg sync. - [2bafeec1b98cfa813fa75dfafa74fdacae8e32c4] - -* Fri Jan 29 2021 Dumitru Ceara - 20.12.0-42 -- ofctrl: Rename 'nb_cfg' to 'req_cfg'. - [c6f4b3a47571d87149b86c999b78509185da7647] - -* Thu Jan 28 2021 Lorenzo Bianconi - 20.12.0-41 -- northd: add --event option to enable controller_event for empty_lb - [8bcee6092b931caa936ee8ac715cf6ec89d3f18d] - -* Thu Jan 28 2021 Lorenzo Bianconi - 20.12.0-40 -- ovn-nbctl: add ecmp/ecmp-symmetric-reply to lr-route-list command - [5e1bb597df512510dc82ce47f9b65a02e2fb5c0b] - -* Thu Jan 28 2021 Lorenzo Bianconi - 20.12.0-39 -- ovn-nbctl: add bfd report to lr-route-list command - [8770192b3b4732e02679f723ea5903a515c6bd8a] - -* Thu Jan 28 2021 Lorenzo Bianconi - 20.12.0-38 -- controller: fix pkt_marking with IP buffering - [5b75b36198b1cdf66aa0bee5a0a73f1e591af1b2] - -* Wed Jan 27 2021 Numan Siddique - 20.12.0-37 -- ovn-ctl: Add support for ovsdb-server --disable-file-column-diff. - [15eefe928ea2a51c7ad03356821f0665ca6abb6d] - -* Wed Jan 27 2021 Numan Siddique - 20.12.0-36 -- ovn-controller: Fix wrong conj_id match flows when caching is enabled. - [11f75ad5bef3d2f6a9d72a8b27468fc3ccfc3d7e] - -* Mon Jan 25 2021 Dumitru Ceara - 20.12.0-35 -- northd: Fix duplicate logical port detection. (#1918582) - [46a4e3bb3a6d01c96721761a0e03d093583ab1cc] - -* Wed Jan 20 2021 Dumitru Ceara - 20.12.0-34 -- binding: Always delete child port bindings first. - [47afa0664d6a41d0a75a65f0ba927974d957cb62] - -* Wed Jan 20 2021 Dumitru Ceara - 20.12.0-33 -- binding: Fix container port removal from local bindings. - [44955fb2395677c9d9bb1afa3985b24317c84431] - -* Wed Jan 20 2021 Dumitru Ceara - 20.12.0-32 -- northd: Fix ACL fair log meters for Port_Group ACLs. - [d2b69af321ad8064d42aad2fd3d15857334e2d63] - -* Wed Jan 20 2021 Numan Siddique - 20.12.0-31 -- Change the default gerrit branch to ovn2.13-20.12. - [4bc45f3897cfdcdc48c354bf69016350d36f9ed9] - -* Wed Jan 13 2021 Lorenzo Bianconi - 20.12.0-30 -- bfd: introduce IPv6 support - [9f42e93b6a25bff87074156586505a6e8968f8cb] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-29 -- ovn: integrate bfd for static routes. - [986137dc1d4dc6905a7c5ab5e279856260966e12] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-28 -- bfd: support demand mode on rx side. - [a3a3062985cadc2f2193b10ccb3404d587028c61] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-27 -- controller: bfd: introduce BFD state machine. - [e75d53c69261a0b104c75d8f6f7dc7175a690833] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-26 -- action: introduce handle_bfd_msg() action. - [2d71cf47fdb194287719a97ee81dbb0dd9fab9d8] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-25 -- controller: introduce BFD tx path in ovn-controller. - [2473b80f778654f0204d1cf4671e543cb6467d5f] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-24 -- ovn-northd: move NAT, Defrag and lb to a function. - [7699c1043a3fec9eb215fc430202ca01846c505e] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-23 -- ovn-northd: Move ipv4 input to a function. - [761f760a42d97184c870e892d299587e657a2c52] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-22 -- ovn-northd: Move lrouter arp and nd datapath processing to a function. - [34c2afc7d49f735e825e0d01bf1b2b64bb277f76] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-21 -- ovn-northd: split build_lswitch_output_port_sec into iterators. - [a6b4b14ac1b6523f85fb13a7f259d9698a70444f] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-20 -- ovn-northd: Move destination handling into functions. - [137b049777cfc301eadba8a2c3b55764bde6f451] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-19 -- ovn-northd: Move broadcast and multicast lookup in lswitch to a function. - [9e60b5574786c0ef8f6403ac61567553c1a7717f] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-18 -- ovn-northd: Move ARP response for external ports to a function. - [d63444b7fcdcc2c68b7af94090410bc3e40e574b] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-17 -- ovn-northd: Move DNS and DHCP defaults to a function. - [502d52712bca01f237aa15e5853bc3090e6034e5] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-16 -- ovn-northd: Move lswitch DNS lookup and response to a function. - [685d26ba45965b2268fbbc36d167115419321f25] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-15 -- ovn-northd: Move DHCP Options and Response to a function. - [e513bafe5718f42844f41d248ddf1777b71aaa50] - -* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-14 -- ovn-northd: Move lswitch ARP/ND Responder to functions. - [f21c1b7a467a691847b5552d4570af706fcc5bb0] - -* Fri Jan 08 2021 Dumitru Ceara - 20.12.0-13 -- binding: Do not clear container lbinding->pb when parent is deleted. - [0ec31292fc29d2c111927382b13ea8af0499f6ac] - -* Fri Jan 08 2021 Dumitru Ceara - 20.12.0-12 -- ovn-trace: fix trigger_event warning. - [90bcf225ced8c80caa44f091665b38ae781ee77b] - -* Fri Dec 18 2020 Numan Siddique - 20.12.0-11 -- tests: Make "ovn -- ovn-controller incremental processing" more reliable. - [7a56fc4309bf1103efb1160a2c2defa8c8e8be28] - -* Fri Dec 18 2020 Numan Siddique - 20.12.0-10 -- osx: Fix compilation error. - [b879b6c9350fef6cce96343aa052957eaf6268bd] - -* Fri Dec 18 2020 Numan Siddique - 20.12.0-9 -- northd: Add ECMP support to router policies. (#1881826) - [20575af6dc17edd48e54c831a6f15cca295a1b35] - -* Fri Dec 18 2020 Numan Siddique - 20.12.0-8 -- nbctl: Remove column verification for partial updates. - [455e46cd53152bcd577e775c012dd76cd7f2611c] - -* Fri Dec 18 2020 Numan Siddique - 20.12.0-7 -- nbctl: Use partial set updates instead of re-setting the whole column. - [6c658bc2a566558531d751006d7ec0c7bdc6ef76] - -* Fri Dec 18 2020 Numan Siddique - 20.12.0-6 -- nbctl: Cache to which switch or router particular port belongs. - [9a4ad65122e461ac5764ff9a0d726d46a4502e7f] - -* Fri Dec 18 2020 Numan Siddique - 20.12.0-5 -- northd: add reject action for lb with no backends - [e0aee2d667b64234888cc4b69dc24dc8e9e57770] - -* Fri Dec 18 2020 Numan Siddique - 20.12.0-4 -- Add openvswitch-2.14.90 dir. - [60e2f264de6cb92c37dd76856954bf16276510d4] -