diff mbox series

[ovs-dev,v2,29/32] northd: Support active-active lrps.

Message ID e9c2c677475a95ec681ac6fb6ccf8b0aa88d9110.1730713432.git.felix.huettner@stackit.cloud
State Superseded
Headers show
Series OVN Fabric integration | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success

Commit Message

Felix Huettner Nov. 4, 2024, 11:04 a.m. UTC
In case we find a LRP (or a LSP connected to a LRP) that has
options:active-active-lrp set we ignore it during normal processing in
join_logical_ports.

We add an additional section at the end where we then use these ports to
generate derived Port_Bindings for each LRP + LSP combination once for
each matching ovn-aa-port-mappings entry.

In the end this gives us the same result as if someone would have
precreated a LRP + LSP combination for each ovn-aa-port-mappings in the
northbound. However it allows our users to benefit from active-active
routing without their CMS needing to know about this feature (besides
the active-active-lrp setting).

Signed-off-by: Felix Huettner <felix.huettner@stackit.cloud>
---
 lib/automake.mk          |   2 +
 lib/lrp-index.c          |  43 +++++++
 lib/lrp-index.h          |  25 ++++
 lib/ovn-util.c           |  97 ++++++++++++++
 lib/ovn-util.h           |  11 ++
 northd/en-northd.c       |   4 +
 northd/inc-proc-northd.c |   6 +
 northd/northd.c          | 272 +++++++++++++++++++++++++++++++++++++--
 northd/northd.h          |  10 ++
 9 files changed, 456 insertions(+), 14 deletions(-)
 create mode 100644 lib/lrp-index.c
 create mode 100644 lib/lrp-index.h
diff mbox series

Patch

diff --git a/lib/automake.mk b/lib/automake.mk
index b69e854b0..90b8ed5a4 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -26,6 +26,8 @@  lib_libovn_la_SOURCES = \
 	lib/ovn-parallel-hmap.c \
 	lib/ip-mcast-index.c \
 	lib/ip-mcast-index.h \
+	lib/lrp-index.c \
+	lib/lrp-index.h \
 	lib/mac-binding-index.c \
 	lib/mac-binding-index.h \
 	lib/mcast-group-index.c \
diff --git a/lib/lrp-index.c b/lib/lrp-index.c
new file mode 100644
index 000000000..ac64c4b45
--- /dev/null
+++ b/lib/lrp-index.c
@@ -0,0 +1,43 @@ 
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "lib/lrp-index.h"
+#include "lib/ovn-nb-idl.h"
+
+struct ovsdb_idl_index *
+lrp_index_create(struct ovsdb_idl *idl)
+{
+    return ovsdb_idl_index_create1(idl, &nbrec_logical_router_port_col_name);
+}
+
+
+/* Finds and returns the lrp with the given 'name', or NULL if no such
+ * lrp exists. */
+const struct nbrec_logical_router_port *
+lrp_lookup_by_name(struct ovsdb_idl_index *nbrec_lrp_by_name,
+                   const char *name)
+{
+    struct nbrec_logical_router_port *target =
+        nbrec_logical_router_port_index_init_row(nbrec_lrp_by_name);
+    nbrec_logical_router_port_index_set_name(target, name);
+
+    struct nbrec_logical_router_port *retval =
+        nbrec_logical_router_port_index_find(nbrec_lrp_by_name, target);
+
+    nbrec_logical_router_port_index_destroy_row(target);
+
+    return retval;
+}
+
diff --git a/lib/lrp-index.h b/lib/lrp-index.h
new file mode 100644
index 000000000..2c56933fc
--- /dev/null
+++ b/lib/lrp-index.h
@@ -0,0 +1,25 @@ 
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVN_LRP_INDEX_H
+#define OVN_LRP_INDEX_H 1
+
+struct ovsdb_idl;
+
+struct ovsdb_idl_index *lrp_index_create(struct ovsdb_idl *);
+
+const struct nbrec_logical_router_port *lrp_lookup_by_name(
+    struct ovsdb_idl_index *nbrec_lrp_by_name, const char *name);
+
+#endif /* lib/lrp-index.h */
diff --git a/lib/ovn-util.c b/lib/ovn-util.c
index 5d0db1a5a..c86f6f209 100644
--- a/lib/ovn-util.c
+++ b/lib/ovn-util.c
@@ -1074,6 +1074,103 @@  get_chassis_external_id_value_bool(const struct smap *external_ids,
     return ret;
 }
 
+bool
+chassis_find_active_active_networks(const struct sbrec_chassis *chassis,
+                                    const char *network_name,
+                                    struct chassis_aa_network
+                                        *chassis_aa_network) {
+    memset(chassis_aa_network, 0, sizeof *chassis_aa_network);
+
+    const char *aa_ports = smap_get(&chassis->other_config,
+                                    "ovn-active-active-mappings");
+    bool found = false;
+    char *curnet, *nextnet, *curport, *nextport, *start;
+
+    /* Structure
+     * ovn-active-active-mappings="<network>|<network>"
+     * network="<network_name>;<port>;<port>"
+     * port="<mac>,<ip>" */
+    nextnet = start = xstrdup(aa_ports);
+    while ((curnet = strsep(&nextnet, "|")) && *curnet) {
+        nextport = curnet;
+        char *network = strsep(&nextport, ";");
+        if (strcmp(network, network_name)) {
+            continue;
+        }
+        found = true;
+        chassis_aa_network->network_name = xstrdup(network);
+        chassis_aa_network->n_addresses = 0;
+        while ((curport = strsep(&nextport, ";")) && *curport) {
+            char *mac, *ip;
+
+            mac = strsep(&curport, ",");
+            ip = curport;
+
+            if (!mac || !ip || !*mac || !*ip) {
+                VLOG_ERR("Invalid format for "
+                         "ovn-active-active-mappings '%s'",
+                         aa_ports);
+                continue;
+            }
+
+            chassis_aa_network->addresses = xrealloc(
+                chassis_aa_network->addresses,
+                (chassis_aa_network->n_addresses + 1
+                 ) * sizeof *chassis_aa_network->addresses);
+            struct lport_addresses *address =
+                &chassis_aa_network->addresses[
+                    chassis_aa_network->n_addresses];
+            init_lport_addresses(address);
+
+            if (!eth_addr_from_string(mac, &address->ea)) {
+                VLOG_ERR("Invalid mac address in "
+                         "ovn-active-active-mappings '%s'",
+                         aa_ports);
+                free(address);
+                continue;
+            }
+            snprintf(address->ea_s, sizeof address->ea_s, ETH_ADDR_FMT,
+                     ETH_ADDR_ARGS(address->ea));
+
+            ovs_be32 ip4;
+            struct in6_addr ip6;
+            unsigned int plen;
+            char *error;
+
+            error = ip_parse_cidr(ip, &ip4, &plen);
+            if (!error) {
+                if (!ip4) {
+                    VLOG_ERR("Invalid ip address in "
+                             "ovn-active-active-mappings '%s'",
+                             aa_ports);
+                    destroy_lport_addresses(address);
+                    continue;
+                }
+
+                add_ipv4_netaddr(address, ip4, plen);
+            } else {
+                free(error);
+
+                error = ipv6_parse_cidr(ip, &ip6, &plen);
+                if (!error) {
+                    add_ipv6_netaddr(address, ip6, plen);
+                } else {
+                    VLOG_ERR("Invalid ip address in "
+                             "ovn-active-active-mappings '%s'",
+                             aa_ports);
+                    destroy_lport_addresses(address);
+                    free(error);
+                    continue;
+                }
+            }
+            chassis_aa_network->n_addresses++;
+        }
+    }
+
+    free(start);
+    return found;
+}
+
 void flow_collector_ids_init(struct flow_collector_ids *ids)
 {
     ovs_list_init(&ids->list);
diff --git a/lib/ovn-util.h b/lib/ovn-util.h
index 3ccd7d003..24ee55a9c 100644
--- a/lib/ovn-util.h
+++ b/lib/ovn-util.h
@@ -44,6 +44,7 @@  struct ovsrec_flow_sample_collector_set_table;
 struct sbrec_datapath_binding;
 struct sbrec_logical_flow;
 struct sbrec_port_binding;
+struct sbrec_chassis;
 struct smap;
 struct svec;
 struct uuid;
@@ -351,6 +352,16 @@  int64_t daemon_startup_ts(void);
 char *lr_lb_address_set_name(uint32_t lr_tunnel_key, int addr_family);
 char *lr_lb_address_set_ref(uint32_t lr_tunnel_key, int addr_family);
 
+struct chassis_aa_network {
+    char *network_name;
+    struct lport_addresses *addresses;
+    size_t n_addresses;
+};
+
+bool chassis_find_active_active_networks(const struct sbrec_chassis *,
+                                         const char *,
+                                         struct chassis_aa_network *);
+
 const char *
 get_chassis_external_id_value(const struct smap *,
                               const char *chassis_id,
diff --git a/northd/en-northd.c b/northd/en-northd.c
index 8152ccbcf..664ace650 100644
--- a/northd/en-northd.c
+++ b/northd/en-northd.c
@@ -41,6 +41,10 @@  static void
 northd_get_input_data(struct engine_node *node,
                       struct northd_input *input_data)
 {
+    input_data->nbrec_lrp_by_name =
+        engine_ovsdb_node_get_index(
+            engine_get_input("NB_logical_router", node),
+            "nbrec_lrp_by_name");
     input_data->sbrec_chassis_by_name =
         engine_ovsdb_node_get_index(
             engine_get_input("SB_chassis", node),
diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
index 99318c633..22227f349 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -19,6 +19,7 @@ 
 #include <stdio.h>
 
 #include "chassis-index.h"
+#include "lrp-index.h"
 #include "ip-mcast-index.h"
 #include "static-mac-binding-index.h"
 #include "lib/inc-proc-eng.h"
@@ -361,6 +362,8 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
         .sb_idl = sb->idl,
     };
 
+    struct ovsdb_idl_index *nbrec_lrp_by_name =
+                         lrp_index_create(nb->idl);
     struct ovsdb_idl_index *sbrec_chassis_by_name =
                          chassis_index_create(sb->idl);
     struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name =
@@ -380,6 +383,9 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
 
     engine_init(&en_northd_output, &engine_arg);
 
+    engine_ovsdb_node_add_index(&en_nb_logical_router,
+                                "nbrec_lrp_by_name",
+                                nbrec_lrp_by_name);
     engine_ovsdb_node_add_index(&en_sb_chassis,
                                 "sbrec_chassis_by_name",
                                 sbrec_chassis_by_name);
diff --git a/northd/northd.c b/northd/northd.c
index 69d86f1c7..6884b7a0e 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -14,6 +14,7 @@ 
 
 #include <config.h>
 
+#include <stdbool.h>
 #include <stdlib.h>
 #include <stdio.h>
 
@@ -30,9 +31,11 @@ 
 #include "hmapx.h"
 #include "openvswitch/hmap.h"
 #include "openvswitch/json.h"
+#include "openvswitch/shash.h"
 #include "ovn/lex.h"
 #include "lb.h"
 #include "lib/chassis-index.h"
+#include "lib/lrp-index.h"
 #include "lib/ip-mcast-index.h"
 #include "lib/static-mac-binding-index.h"
 #include "lib/copp.h"
@@ -1252,6 +1255,11 @@  ovn_port_cleanup(struct ovn_port *port)
     free(port->ps_addrs);
     port->ps_addrs = NULL;
     port->n_ps_addrs = 0;
+    if (port->is_active_active) {
+        ovs_assert(port->aa_chassis_name);
+        free(port->aa_mac);
+        free(port->aa_chassis_name);
+    }
 
     destroy_lport_addresses(&port->lrp_networks);
     destroy_lport_addresses(&port->proxy_arp_addrs);
@@ -1433,6 +1441,32 @@  lrport_is_enabled(const struct nbrec_logical_router_port *lrport)
     return !lrport->enabled || *lrport->enabled;
 }
 
+static bool
+lrport_is_active_active(const struct nbrec_logical_router_port *lrport)
+{
+    if (!lrport) {
+      return false;
+    }
+    return smap_get_bool(&lrport->options, "active-active-lrp", false);
+}
+
+static const struct nbrec_logical_router_port*
+lsp_get_peer(struct ovsdb_idl_index *nbrec_lrp_by_name,
+                     const struct nbrec_logical_switch_port *nbsp)
+{
+    if (!lsp_is_router(nbsp)) {
+        return NULL;
+    }
+
+    const char *peer_name = smap_get(&nbsp->options, "router-port");
+    if (!peer_name) {
+        return NULL;
+    }
+
+    return lrp_lookup_by_name(nbrec_lrp_by_name, peer_name);
+}
+
+
 static bool
 lsp_force_fdb_lookup(const struct ovn_port *op)
 {
@@ -1462,6 +1496,18 @@  ovn_port_get_peer(const struct hmap *lr_ports, struct ovn_port *op)
     return ovn_port_find(lr_ports, peer_name);
 }
 
+static const char *
+ovn_port_get_mac(struct ovn_port *op)
+{
+    if (op->is_active_active) {
+        return op->aa_mac;
+    } else if (op->primary_port && op->primary_port->is_active_active) {
+        return op->primary_port->aa_mac;
+    } else {
+        return op->nbrp->mac;
+    }
+}
+
 static void
 ipam_insert_ip_for_datapath(struct ovn_datapath *od, uint32_t ip, bool dynamic)
 {
@@ -2295,13 +2341,19 @@  join_logical_ports_lrp(struct hmap *ports,
     return op;
 }
 
+struct active_active_port {
+    const struct nbrec_logical_switch_port *nbsp;
+    const struct nbrec_logical_router_port *nbrp;
+    struct ovn_datapath *switch_dp;
+    struct ovn_datapath *router_dp;
+};
+
 
 static struct ovn_port *
 create_cr_port(struct ovn_port *op, struct hmap *ports,
                struct ovs_list *both_dbs, struct ovs_list *nb_only)
 {
-    char *redirect_name = ovn_chassis_redirect_name(
-        op->nbsp ? op->nbsp->name : op->nbrp->name);
+    char *redirect_name = ovn_chassis_redirect_name(op->key);
 
     struct ovn_port *crp = ovn_port_find(ports, redirect_name);
     if (crp && crp->sb && crp->sb->datapath == op->od->sb) {
@@ -2346,6 +2398,8 @@  peer_needs_cr_port_creation(struct ovn_port *op)
 
 static void
 join_logical_ports(const struct sbrec_port_binding_table *sbrec_pb_table,
+                   struct ovsdb_idl_index *nbrec_lrp_by_name,
+                   struct ovsdb_idl_index *sbrec_chassis_by_name,
                    struct hmap *ls_datapaths, struct hmap *lr_datapaths,
                    struct hmap *ports, unsigned long *queue_id_bitmap,
                    struct hmap *tag_alloc_table, struct ovs_list *sb_only,
@@ -2355,6 +2409,8 @@  join_logical_ports(const struct sbrec_port_binding_table *sbrec_pb_table,
     ovs_list_init(nb_only);
     ovs_list_init(both);
 
+    struct shash active_active_ports = SHASH_INITIALIZER(&active_active_ports);
+
     const struct sbrec_port_binding *sb;
     SBREC_PORT_BINDING_TABLE_FOR_EACH (sb, sbrec_pb_table) {
         struct ovn_port *op = ovn_port_create(ports, sb->logical_port,
@@ -2371,6 +2427,20 @@  join_logical_ports(const struct sbrec_port_binding_table *sbrec_pb_table,
                 = od->nbr->ports[i];
 
             struct lport_addresses lrp_networks;
+
+            if (lrport_is_active_active(nbrp)) {
+                struct ovn_port *op = ovn_port_find_bound(ports, nbrp->name);
+                if (op) {
+                    ovs_list_remove(&op->list);
+                }
+                struct active_active_port *aap = xzalloc(
+                    sizeof(struct active_active_port));
+                aap->nbrp = nbrp;
+                aap->router_dp = od;
+                shash_add(&active_active_ports, nbrp->name, aap);
+                continue;
+            }
+
             if (!extract_lrp_networks(nbrp, &lrp_networks)) {
                 static struct vlog_rate_limit rl
                     = VLOG_RATE_LIMIT_INIT(5, 1);
@@ -2388,6 +2458,16 @@  join_logical_ports(const struct sbrec_port_binding_table *sbrec_pb_table,
         for (size_t i = 0; i < od->nbs->n_ports; i++) {
             const struct nbrec_logical_switch_port *nbsp
                 = od->nbs->ports[i];
+            const struct nbrec_logical_router_port *nbrp
+                = lsp_get_peer(nbrec_lrp_by_name, nbsp);
+            if (lrport_is_active_active(nbrp)) {
+                struct active_active_port *aap =
+                    shash_find_data(&active_active_ports, nbrp->name);
+                ovs_assert(aap);
+                aap->nbsp = nbsp;
+                aap->switch_dp = od;
+                continue;
+            }
             join_logical_ports_lsp(ports, nb_only, both, od, nbsp,
                                    nbsp->name, queue_id_bitmap,
                                    tag_alloc_table);
@@ -2466,6 +2546,103 @@  join_logical_ports(const struct sbrec_port_binding_table *sbrec_pb_table,
         }
     }
 
+    /* Now we setup the active-active lrp/lsps */
+    struct shash_node *aa_snode;
+    SHASH_FOR_EACH (aa_snode, &active_active_ports) {
+        const struct active_active_port *aap = aa_snode->data;
+        const struct nbrec_logical_switch_port *nbsp = aap->nbsp;
+        const struct nbrec_logical_router_port *nbrp = aap->nbrp;
+        ovs_assert(nbrp);
+        ovs_assert(aap->switch_dp);
+        ovs_assert(aap->router_dp);
+
+        if (aap->switch_dp->n_localnet_ports != 1) {
+            static struct vlog_rate_limit rl
+                = VLOG_RATE_LIMIT_INIT(5, 1);
+            VLOG_WARN_RL(&rl, "active-active lrp '%s' is not connect to a "
+                              "ls with exactly one localnet port", nbrp->name);
+            continue;
+        }
+
+        const struct ovn_port *localnet_port =
+            aap->switch_dp->localnet_ports[0];
+
+        const char *network_name =
+            smap_get(&localnet_port->nbsp->options, "network_name");
+        if (!network_name) {
+            static struct vlog_rate_limit rl
+                = VLOG_RATE_LIMIT_INIT(5, 1);
+            VLOG_WARN_RL(&rl, "active-active lrp '%s' has a localnet port "
+                              "connected with no network_name", nbrp->name);
+            continue;
+        }
+
+        if (!nbrp->ha_chassis_group) {
+            static struct vlog_rate_limit rl
+                = VLOG_RATE_LIMIT_INIT(5, 1);
+            VLOG_WARN_RL(&rl, "missing 'ha_chassis_group' for"
+                " active-active-port %s", nbrp->name);
+            continue;
+        }
+
+        for (size_t i = 0; i < nbrp->ha_chassis_group->n_ha_chassis; i++) {
+            const struct nbrec_ha_chassis *hc
+              = nbrp->ha_chassis_group->ha_chassis[i];
+
+            const struct sbrec_chassis *chassis = chassis_lookup_by_name(
+                sbrec_chassis_by_name, hc->chassis_name);
+            if (!chassis) {
+                static struct vlog_rate_limit rl
+                    = VLOG_RATE_LIMIT_INIT(5, 1);
+                VLOG_WARN_RL(&rl, "'ha_chassis_group' contains not found"
+                    " chassis %s", hc->chassis_name);
+                continue;
+            }
+
+            struct chassis_aa_network networks;
+            if (!chassis_find_active_active_networks(chassis, network_name,
+                                                     &networks)) {
+                static struct vlog_rate_limit rl
+                    = VLOG_RATE_LIMIT_INIT(5, 1);
+                VLOG_WARN_RL(&rl, "chassis %s does not contain network"
+                    " but it is in ha_chassis_group", chassis->name);
+                continue;
+            }
+
+            for (size_t j = 0; j < networks.n_addresses; j++) {
+                char *lrp_name = xasprintf("%s-%s-%"PRIuSIZE,
+                                           nbrp->name, chassis->name, j);
+                char *lsp_name = xasprintf("%s-%s-%"PRIuSIZE,
+                                           nbsp->name, chassis->name, j);
+                struct ovn_port *lrp =
+                    join_logical_ports_lrp(ports, nb_only, both, &dgps,
+                                           aap->router_dp, nbrp,
+                                           lrp_name, &networks.addresses[j]);
+                struct ovn_port *lsp =
+                    join_logical_ports_lsp(ports, nb_only, both,
+                                           aap->switch_dp, nbsp,
+                                     lsp_name, queue_id_bitmap,
+                                           tag_alloc_table);
+                free(lrp_name);
+                free(lsp_name);
+                if (!lrp || !lsp) {
+                    continue;
+                }
+                lrp->peer = lsp;
+                lsp->peer = lrp;
+                lrp->is_active_active = true;
+                lsp->is_active_active = true;
+                lrp->aa_mac = xstrdup(networks.addresses[j].ea_s);
+                lrp->aa_chassis_name = xstrdup(chassis->name);
+                lsp->aa_chassis_name = xstrdup(chassis->name);
+                lrp->aa_chassis_index = j;
+                lsp->aa_chassis_index = j;
+            }
+            free(networks.network_name);
+            free(networks.addresses);
+        }
+    }
+
     struct hmapx_node *hmapx_node;
     HMAPX_FOR_EACH (hmapx_node, &dgps) {
         op = hmapx_node->data;
@@ -2523,6 +2700,8 @@  join_logical_ports(const struct sbrec_port_binding_table *sbrec_pb_table,
     HMAP_FOR_EACH (op, key_node, ports) {
         ipam_add_port_addresses(op->od, op);
     }
+
+    shash_destroy_free_data(&active_active_ports);
 }
 
 /* Returns an array of strings, each consisting of a MAC address followed
@@ -2876,6 +3055,51 @@  sync_ha_chassis_group_for_sbpb(
     sbrec_port_binding_set_ha_chassis_group(pb, sb_ha_grp);
 }
 
+static char *
+generate_ha_chassis_group_active_active(
+    struct ovsdb_idl_txn *ovnsb_txn,
+    struct ovsdb_idl_index *sbrec_chassis_by_name,
+    struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name,
+    const char *chassis_name,
+    const struct sbrec_port_binding *pb)
+{
+    bool new_sb_chassis_group = false;
+    char *chassis_group_name = xasprintf(
+        "active-active-fixed-%s", chassis_name);
+    const struct sbrec_ha_chassis_group *sb_ha_grp =
+        ha_chassis_group_lookup_by_name(
+            sbrec_ha_chassis_grp_by_name, chassis_group_name);
+
+    if (!sb_ha_grp) {
+        sb_ha_grp = sbrec_ha_chassis_group_insert(ovnsb_txn);
+        sbrec_ha_chassis_group_set_name(sb_ha_grp, chassis_group_name);
+        new_sb_chassis_group = true;
+    }
+
+    if (new_sb_chassis_group) {
+        struct sbrec_ha_chassis **sb_ha_chassis = NULL;
+        sb_ha_chassis = xcalloc(1, sizeof *sb_ha_chassis);
+        const struct sbrec_chassis *chassis =
+            chassis_lookup_by_name(sbrec_chassis_by_name, chassis_name);
+        sb_ha_chassis[0] = sbrec_ha_chassis_insert(ovnsb_txn);
+        /* It's perfectly ok if the chassis is NULL. This could
+         * happen when ovn-controller exits and removes its row
+         * from the chassis table in OVN SB DB. */
+        sbrec_ha_chassis_set_chassis(sb_ha_chassis[0], chassis);
+        sbrec_ha_chassis_set_priority(sb_ha_chassis[0], 1);
+        const struct smap external_ids =
+            SMAP_CONST1(&external_ids, "chassis-name",
+                        chassis_name);
+        sbrec_ha_chassis_set_external_ids(sb_ha_chassis[0], &external_ids);
+        sbrec_ha_chassis_group_set_ha_chassis(sb_ha_grp, sb_ha_chassis,
+                                              1);
+        free(sb_ha_chassis);
+    }
+
+    sbrec_port_binding_set_ha_chassis_group(pb, sb_ha_grp);
+    return chassis_group_name;
+}
+
 /* This functions translates the gw chassis on the nb database
  * to HA chassis group in the sb database entries.
  */
@@ -3130,14 +3354,29 @@  ovn_port_update_sbrec(struct ovsdb_idl_txn *ovnsb_txn,
                                  "ignoring the latter.", op->nbrp->name);
                 }
 
-                /* HA Chassis group is set. Ignore 'gateway_chassis'. */
-                sync_ha_chassis_group_for_sbpb(ovnsb_txn,
-                                               sbrec_chassis_by_name,
-                                               sbrec_ha_chassis_grp_by_name,
-                                               op->nbrp->ha_chassis_group,
-                                               op->sb);
-                sset_add(active_ha_chassis_grps,
-                         op->nbrp->ha_chassis_group->name);
+                if (op->primary_port && op->primary_port->is_active_active) {
+
+                    /* Generate new HA Chassis group just bound to one node. */
+                    char *ha_chassis_group =
+                        generate_ha_chassis_group_active_active(ovnsb_txn,
+                                           sbrec_chassis_by_name,
+                                           sbrec_ha_chassis_grp_by_name,
+                                           op->primary_port->aa_chassis_name,
+                                           op->sb);
+                    sset_add(active_ha_chassis_grps,
+                             ha_chassis_group);
+                    free(ha_chassis_group);
+                } else {
+
+                    /* HA Chassis group is set. Ignore 'gateway_chassis'. */
+                    sync_ha_chassis_group_for_sbpb(ovnsb_txn,
+                                           sbrec_chassis_by_name,
+                                           sbrec_ha_chassis_grp_by_name,
+                                           op->nbrp->ha_chassis_group,
+                                           op->sb);
+                    sset_add(active_ha_chassis_grps,
+                             op->nbrp->ha_chassis_group->name);
+                }
             } else if (op->nbrp->n_gateway_chassis) {
                 /* Legacy gateway_chassis support.
                  * Create ha_chassis_group for the Northbound gateway_chassis
@@ -4252,6 +4491,7 @@  build_ports(struct ovsdb_idl_txn *ovnsb_txn,
     const struct sbrec_mac_binding_table *sbrec_mac_binding_table,
     const struct sbrec_ha_chassis_group_table *sbrec_ha_chassis_group_table,
     const struct sbrec_route_table *sbrec_route_table,
+    struct ovsdb_idl_index *nbrec_lrp_by_name,
     struct ovsdb_idl_index *sbrec_chassis_by_name,
     struct ovsdb_idl_index *sbrec_chassis_by_hostname,
     struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name,
@@ -4272,7 +4512,10 @@  build_ports(struct ovsdb_idl_txn *ovnsb_txn,
     /* Borrow ls_ports for joining NB and SB for both LSPs and LRPs.
      * We will split them later. */
     struct hmap *ports = ls_ports;
-    join_logical_ports(sbrec_port_binding_table, ls_datapaths, lr_datapaths,
+    join_logical_ports(sbrec_port_binding_table,
+                       nbrec_lrp_by_name,
+                       sbrec_chassis_by_name,
+                       ls_datapaths, lr_datapaths,
                        ports, queue_id_bitmap,
                        &tag_alloc_table, &sb_only, &nb_only, &both);
 
@@ -12974,7 +13217,7 @@  build_lrouter_icmp_packet_toobig_admin_flows(
                   " (ip6 && icmp6.type == 2 && icmp6.code == 0)) &&"
                   " eth.dst == %s && !is_chassis_resident(%s) &&"
                   " flags.tunnel_rx == 1",
-                  op->nbrp->mac, op->cr_port->json_key);
+                  ovn_port_get_mac(op), op->cr_port->json_key);
     ds_clear(actions);
     ds_put_format(actions, "outport <-> inport; inport = %s; next;",
                   op->json_key);
@@ -13017,7 +13260,7 @@  build_lswitch_icmp_packet_toobig_admin_flows(
                       "((ip4 && icmp4.type == 3 && icmp4.code == 4) ||"
                       " (ip6 && icmp6.type == 2 && icmp6.code == 0)) && "
                       "eth.src == %s && outport == %s && flags.tunnel_rx == 1",
-                      peer->nbrp->mac, op->json_key);
+                      ovn_port_get_mac(peer), op->json_key);
         ovn_lflow_add(lflows, op->od, S_SWITCH_IN_CHECK_PORT_SEC, 120,
                       ds_cstr(match), "outport <-> inport; next;",
                       op->lflow_ref);
@@ -13026,7 +13269,7 @@  build_lswitch_icmp_packet_toobig_admin_flows(
                       "((ip4 && icmp4.type == 3 && icmp4.code == 4) ||"
                       " (ip6 && icmp6.type == 2 && icmp6.code == 0)) && "
                       "eth.dst == %s && flags.tunnel_rx == 1",
-                      peer->nbrp->mac);
+                      ovn_port_get_mac(peer));
         ds_clear(actions);
         ds_put_format(actions,
                       "outport <-> inport; next(pipeline=ingress,table=%d);",
@@ -19055,6 +19298,7 @@  ovnnb_db_run(struct northd_input *input_data,
                 input_data->sbrec_mac_binding_table,
                 input_data->sbrec_ha_chassis_group_table,
                 input_data->sbrec_route_table,
+                input_data->nbrec_lrp_by_name,
                 input_data->sbrec_chassis_by_name,
                 input_data->sbrec_chassis_by_hostname,
                 input_data->sbrec_ha_chassis_grp_by_name,
diff --git a/northd/northd.h b/northd/northd.h
index 75ad86973..12fe83440 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -66,6 +66,7 @@  struct northd_input {
     const struct chassis_features *features;
 
     /* Indexes */
+    struct ovsdb_idl_index *nbrec_lrp_by_name;
     struct ovsdb_idl_index *sbrec_chassis_by_name;
     struct ovsdb_idl_index *sbrec_chassis_by_hostname;
     struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name;
@@ -664,6 +665,15 @@  struct ovn_port {
     /* Only used for the router type LSP whose peer is l3dgw_port */
     bool enable_router_port_acl;
 
+    /* Used for active-active port bindings to store the data they where
+     * generated from */
+    bool is_active_active;
+    char *aa_chassis_name;
+    size_t aa_chassis_index;
+    /* The following value is only set on the lrp side of an
+     * active-active port binding */
+    char *aa_mac;
+
     /* Reference of lflows generated for this ovn_port.
      *
      * This data is initialized and destroyed by the en_northd node, but