diff mbox

[ovs-dev,v2,1/4] ovn: l3ha, handling of multiple gateways

Message ID 1496930708-15441-1-git-send-email-majopela@redhat.com
State Superseded
Delegated to: Guru Shetty
Headers show

Commit Message

Miguel Angel Ajo June 8, 2017, 2:05 p.m. UTC
From: Miguel Angel Ajo <majopela@redhat.com>

This patch handles multiple gateways with priorities in chassisredirect
ports, any gateway with a chassis redirect port will implement the
rules to de-encapsulate incomming packets for such port.

And hosts targetting a remote chassisredirect port will setup a
bundle(active_backup, ..) action to each tunnel port, in the given
priority order.

Signed-off-by: Miguel Angel Ajo <majopela@redhat.com>
---
 ovn/controller/binding.c        |   9 +--
 ovn/controller/lflow.c          |   6 +-
 ovn/controller/lport.c          | 119 ++++++++++++++++++++++++++++++++++++++++
 ovn/controller/lport.h          |  28 ++++++++++
 ovn/controller/ovn-controller.c |   5 +-
 ovn/controller/physical.c       | 114 ++++++++++++++++++++++++++++++++------
 tests/ovn.at                    | 114 ++++++++++++++++++++++++++++++++++++++
 7 files changed, 369 insertions(+), 26 deletions(-)

Comments

Ben Pfaff June 8, 2017, 9:39 p.m. UTC | #1
On Thu, Jun 08, 2017 at 02:05:05PM +0000, majopela@redhat.com wrote:
> From: Miguel Angel Ajo <majopela@redhat.com>
> 
> This patch handles multiple gateways with priorities in chassisredirect
> ports, any gateway with a chassis redirect port will implement the
> rules to de-encapsulate incomming packets for such port.
> 
> And hosts targetting a remote chassisredirect port will setup a
> bundle(active_backup, ..) action to each tunnel port, in the given
> priority order.
> 
> Signed-off-by: Miguel Angel Ajo <majopela@redhat.com>

I feel unqualified to fully and properly review this series.  Guru, is
it something you'd feel able to take a look at?  Is anyone else planning
to review this?

Thanks,

Ben.
Miguel Angel Ajo June 9, 2017, 9:35 a.m. UTC | #2
Thank you Ben and Guru,

    I plan to generalise this work with follow up patches, to let us
setup a multiple chassis on a Logical_Router too (and hence handle it
via the l3gateway ports).

   Anil Venkata is also working on a follow up patch to make sure
gARPs are only  announced on the master/active gateway.


On Thu, Jun 8, 2017 at 11:39 PM, Ben Pfaff <blp@ovn.org> wrote:

> On Thu, Jun 08, 2017 at 02:05:05PM +0000, majopela@redhat.com wrote:
> > From: Miguel Angel Ajo <majopela@redhat.com>
> >
> > This patch handles multiple gateways with priorities in chassisredirect
> > ports, any gateway with a chassis redirect port will implement the
> > rules to de-encapsulate incomming packets for such port.
> >
> > And hosts targetting a remote chassisredirect port will setup a
> > bundle(active_backup, ..) action to each tunnel port, in the given
> > priority order.
> >
> > Signed-off-by: Miguel Angel Ajo <majopela@redhat.com>
>
> I feel unqualified to fully and properly review this series.  Guru, is
> it something you'd feel able to take a look at?  Is anyone else planning
> to review this?
>
> Thanks,
>
> Ben.
>
Miguel Angel Ajo June 9, 2017, 10:35 a.m. UTC | #3
I'd like this feature to be considered for OVS 2.8,

I know the deadline for branching is close, but this would let us implement
l3ha on the networking-ovn side, closing one of the gaps we have
with the reference implementation now.


Best regards,
Miguel Ángel

On Fri, Jun 9, 2017 at 11:35 AM, Miguel Angel Ajo Pelayo <
majopela@redhat.com> wrote:

> Thank you Ben and Guru,
>
>     I plan to generalise this work with follow up patches, to let us
> setup a multiple chassis on a Logical_Router too (and hence handle it
> via the l3gateway ports).
>
>    Anil Venkata is also working on a follow up patch to make sure
> gARPs are only  announced on the master/active gateway.
>
>
> On Thu, Jun 8, 2017 at 11:39 PM, Ben Pfaff <blp@ovn.org> wrote:
>
>> On Thu, Jun 08, 2017 at 02:05:05PM +0000, majopela@redhat.com wrote:
>> > From: Miguel Angel Ajo <majopela@redhat.com>
>> >
>> > This patch handles multiple gateways with priorities in chassisredirect
>> > ports, any gateway with a chassis redirect port will implement the
>> > rules to de-encapsulate incomming packets for such port.
>> >
>> > And hosts targetting a remote chassisredirect port will setup a
>> > bundle(active_backup, ..) action to each tunnel port, in the given
>> > priority order.
>> >
>> > Signed-off-by: Miguel Angel Ajo <majopela@redhat.com>
>>
>> I feel unqualified to fully and properly review this series.  Guru, is
>> it something you'd feel able to take a look at?  Is anyone else planning
>> to review this?
>>
>> Thanks,
>>
>> Ben.
>>
>
>
Gurucharan Shetty June 9, 2017, 2:46 p.m. UTC | #4
On 8 June 2017 at 14:39, Ben Pfaff <blp@ovn.org> wrote:

> On Thu, Jun 08, 2017 at 02:05:05PM +0000, majopela@redhat.com wrote:
> > From: Miguel Angel Ajo <majopela@redhat.com>
> >
> > This patch handles multiple gateways with priorities in chassisredirect
> > ports, any gateway with a chassis redirect port will implement the
> > rules to de-encapsulate incomming packets for such port.
> >
> > And hosts targetting a remote chassisredirect port will setup a
> > bundle(active_backup, ..) action to each tunnel port, in the given
> > priority order.
> >
> > Signed-off-by: Miguel Angel Ajo <majopela@redhat.com>
>
> I feel unqualified to fully and properly review this series.  Guru, is
> it something you'd feel able to take a look at?  Is anyone else planning
> to review this?
>

I will have a go at it.

>
> Thanks,
>
> Ben.
>
Russell Bryant June 13, 2017, 5:17 p.m. UTC | #5
On Fri, Jun 9, 2017 at 10:46 AM, Guru Shetty <guru@ovn.org> wrote:
> On 8 June 2017 at 14:39, Ben Pfaff <blp@ovn.org> wrote:
>
>> On Thu, Jun 08, 2017 at 02:05:05PM +0000, majopela@redhat.com wrote:
>> > From: Miguel Angel Ajo <majopela@redhat.com>
>> >
>> > This patch handles multiple gateways with priorities in chassisredirect
>> > ports, any gateway with a chassis redirect port will implement the
>> > rules to de-encapsulate incomming packets for such port.
>> >
>> > And hosts targetting a remote chassisredirect port will setup a
>> > bundle(active_backup, ..) action to each tunnel port, in the given
>> > priority order.
>> >
>> > Signed-off-by: Miguel Angel Ajo <majopela@redhat.com>
>>
>> I feel unqualified to fully and properly review this series.  Guru, is
>> it something you'd feel able to take a look at?  Is anyone else planning
>> to review this?
>>
>
> I will have a go at it.

Thanks a lot, Guru.  Since this is important for OpenStack, let me
know if you won't have time to review and I'll make time to help.  I
think you're a better reviewer for this one, though.
Gurucharan Shetty June 13, 2017, 5:22 p.m. UTC | #6
On 13 June 2017 at 10:17, Russell Bryant <russell@ovn.org> wrote:

> On Fri, Jun 9, 2017 at 10:46 AM, Guru Shetty <guru@ovn.org> wrote:
> > On 8 June 2017 at 14:39, Ben Pfaff <blp@ovn.org> wrote:
> >
> >> On Thu, Jun 08, 2017 at 02:05:05PM +0000, majopela@redhat.com wrote:
> >> > From: Miguel Angel Ajo <majopela@redhat.com>
> >> >
> >> > This patch handles multiple gateways with priorities in
> chassisredirect
> >> > ports, any gateway with a chassis redirect port will implement the
> >> > rules to de-encapsulate incomming packets for such port.
> >> >
> >> > And hosts targetting a remote chassisredirect port will setup a
> >> > bundle(active_backup, ..) action to each tunnel port, in the given
> >> > priority order.
> >> >
> >> > Signed-off-by: Miguel Angel Ajo <majopela@redhat.com>
> >>
> >> I feel unqualified to fully and properly review this series.  Guru, is
> >> it something you'd feel able to take a look at?  Is anyone else planning
> >> to review this?
> >>
> >
> > I will have a go at it.
>
> Thanks a lot, Guru.  Since this is important for OpenStack, let me
> know if you won't have time to review and I'll make time to help.  I
> think you're a better reviewer for this one, though.
>
Russell,
 If you want to do the initial round, please go ahead. Since you know more
about the usecase and how it is to be used by OpenStack, your review will
help.


>
> --
> Russell Bryant
>
Russell Bryant June 13, 2017, 5:33 p.m. UTC | #7
On Tue, Jun 13, 2017 at 1:22 PM, Guru Shetty <guru@ovn.org> wrote:
>
>
> On 13 June 2017 at 10:17, Russell Bryant <russell@ovn.org> wrote:
>>
>> On Fri, Jun 9, 2017 at 10:46 AM, Guru Shetty <guru@ovn.org> wrote:
>> > On 8 June 2017 at 14:39, Ben Pfaff <blp@ovn.org> wrote:
>> >
>> >> On Thu, Jun 08, 2017 at 02:05:05PM +0000, majopela@redhat.com wrote:
>> >> > From: Miguel Angel Ajo <majopela@redhat.com>
>> >> >
>> >> > This patch handles multiple gateways with priorities in
>> >> > chassisredirect
>> >> > ports, any gateway with a chassis redirect port will implement the
>> >> > rules to de-encapsulate incomming packets for such port.
>> >> >
>> >> > And hosts targetting a remote chassisredirect port will setup a
>> >> > bundle(active_backup, ..) action to each tunnel port, in the given
>> >> > priority order.
>> >> >
>> >> > Signed-off-by: Miguel Angel Ajo <majopela@redhat.com>
>> >>
>> >> I feel unqualified to fully and properly review this series.  Guru, is
>> >> it something you'd feel able to take a look at?  Is anyone else
>> >> planning
>> >> to review this?
>> >>
>> >
>> > I will have a go at it.
>>
>> Thanks a lot, Guru.  Since this is important for OpenStack, let me
>> know if you won't have time to review and I'll make time to help.  I
>> think you're a better reviewer for this one, though.
>
> Russell,
>  If you want to do the initial round, please go ahead. Since you know more
> about the usecase and how it is to be used by OpenStack, your review will
> help.

OK - I'll do a pass on them.
Miguel Angel Ajo July 13, 2017, 1:13 p.m. UTC | #8
This is a fixup and regression test I've written for the l3ha series
for an issue I found while restarting ovn-controllers around.

I hope it looks good, it can be concatenated to the series, or I can
squash it to the ovn-northd patch.
Miguel Angel Ajo July 13, 2017, 1:15 p.m. UTC | #9
Well, the fix up is actually [PATCH v5 1/1] ovn: l3ha ensure no master
bouncing ....

I'm still getting used to git send-email :)

Best regards,
Miguel Ángel Ajo.

On Thu, Jul 13, 2017 at 3:13 PM, Miguel Angel Ajo <majopela@redhat.com>
wrote:

> This is a fixup and regression test I've written for the l3ha series
> for an issue I found while restarting ovn-controllers around.
>
> I hope it looks good, it can be concatenated to the series, or I can
> squash it to the ovn-northd patch.
>
>
Russell Bryant July 15, 2017, 2:08 a.m. UTC | #10
On Thu, Jul 13, 2017 at 9:13 AM, Miguel Angel Ajo <majopela@redhat.com> wrote:
> This is a fixup and regression test I've written for the l3ha series
> for an issue I found while restarting ovn-controllers around.
>
> I hope it looks good, it can be concatenated to the series, or I can
> squash it to the ovn-northd patch.

I replied to the patch itself before seeing this message.  I think it
should be squashed into the ovn-northd patch.
Russell Bryant July 16, 2017, 2:24 p.m. UTC | #11
On Fri, Jul 14, 2017 at 10:08 PM Russell Bryant <russell@ovn.org> wrote:

> On Thu, Jul 13, 2017 at 9:13 AM, Miguel Angel Ajo <majopela@redhat.com>
> wrote:
> > This is a fixup and regression test I've written for the l3ha series
> > for an issue I found while restarting ovn-controllers around.
> >
> > I hope it looks good, it can be concatenated to the series, or I can
> > squash it to the ovn-northd patch.
>
> I replied to the patch itself before seeing this message.  I think it
> should be squashed into the ovn-northd patch.
>
> --
> Russell Bryant


But no need to resubmit just for that. I can do it unless there end up
being more changes needed.

>
> --
Russell Bryant
Russell Bryant July 16, 2017, 5:17 p.m. UTC | #12
On Sun, Jul 16, 2017 at 10:24 AM, Russell Bryant <russell@ovn.org> wrote:
>
> On Fri, Jul 14, 2017 at 10:08 PM Russell Bryant <russell@ovn.org> wrote:
>>
>> On Thu, Jul 13, 2017 at 9:13 AM, Miguel Angel Ajo <majopela@redhat.com>
>> wrote:
>> > This is a fixup and regression test I've written for the l3ha series
>> > for an issue I found while restarting ovn-controllers around.
>> >
>> > I hope it looks good, it can be concatenated to the series, or I can
>> > squash it to the ovn-northd patch.
>>
>> I replied to the patch itself before seeing this message.  I think it
>> should be squashed into the ovn-northd patch.
>>
>> --
>> Russell Bryant
>
>
> But no need to resubmit just for that. I can do it unless there end up being
> more changes needed.

On second thought, the rebase isn't clean so it might be better for
you to do it.  I'll go ahead and review the updates in detail, though.
Russell Bryant July 16, 2017, 7:55 p.m. UTC | #13
On Sun, Jul 16, 2017 at 1:17 PM, Russell Bryant <russell@ovn.org> wrote:
> On Sun, Jul 16, 2017 at 10:24 AM, Russell Bryant <russell@ovn.org> wrote:
>>
>> On Fri, Jul 14, 2017 at 10:08 PM Russell Bryant <russell@ovn.org> wrote:
>>>
>>> On Thu, Jul 13, 2017 at 9:13 AM, Miguel Angel Ajo <majopela@redhat.com>
>>> wrote:
>>> > This is a fixup and regression test I've written for the l3ha series
>>> > for an issue I found while restarting ovn-controllers around.
>>> >
>>> > I hope it looks good, it can be concatenated to the series, or I can
>>> > squash it to the ovn-northd patch.
>>>
>>> I replied to the patch itself before seeing this message.  I think it
>>> should be squashed into the ovn-northd patch.
>>>
>>> --
>>> Russell Bryant
>>
>>
>> But no need to resubmit just for that. I can do it unless there end up being
>> more changes needed.
>
> On second thought, the rebase isn't clean so it might be better for
> you to do it.  I'll go ahead and review the updates in detail, though.

Sorry for so many replies to myself ...

in case you don't see my other reply to the patches, please disregard
this request.  I applied the series to master.
Miguel Angel Ajo July 17, 2017, 12:16 p.m. UTC | #14
Thanks a lot !! :)

I will sync with you to see where can we improve documentation/tutorials,
etc,
and I'll start working with Anil on the networking-ovn integration for
openstack.



On Sun, Jul 16, 2017 at 9:55 PM, Russell Bryant <russell@ovn.org> wrote:

> On Sun, Jul 16, 2017 at 1:17 PM, Russell Bryant <russell@ovn.org> wrote:
> > On Sun, Jul 16, 2017 at 10:24 AM, Russell Bryant <russell@ovn.org>
> wrote:
> >>
> >> On Fri, Jul 14, 2017 at 10:08 PM Russell Bryant <russell@ovn.org>
> wrote:
> >>>
> >>> On Thu, Jul 13, 2017 at 9:13 AM, Miguel Angel Ajo <majopela@redhat.com
> >
> >>> wrote:
> >>> > This is a fixup and regression test I've written for the l3ha series
> >>> > for an issue I found while restarting ovn-controllers around.
> >>> >
> >>> > I hope it looks good, it can be concatenated to the series, or I can
> >>> > squash it to the ovn-northd patch.
> >>>
> >>> I replied to the patch itself before seeing this message.  I think it
> >>> should be squashed into the ovn-northd patch.
> >>>
> >>> --
> >>> Russell Bryant
> >>
> >>
> >> But no need to resubmit just for that. I can do it unless there end up
> being
> >> more changes needed.
> >
> > On second thought, the rebase isn't clean so it might be better for
> > you to do it.  I'll go ahead and review the updates in detail, though.
>
> Sorry for so many replies to myself ...
>
> in case you don't see my other reply to the patches, please disregard
> this request.  I applied the series to master.
>
> --
> Russell Bryant
>
diff mbox

Patch

diff --git a/ovn/controller/binding.c b/ovn/controller/binding.c
index bb76608..d45c5df 100644
--- a/ovn/controller/binding.c
+++ b/ovn/controller/binding.c
@@ -394,12 +394,13 @@  consider_local_datapath(struct controller_ctx *ctx,
                                false, local_datapaths);
         }
     } else if (!strcmp(binding_rec->type, "chassisredirect")) {
-        const char *chassis_id = smap_get(&binding_rec->options,
-                                          "redirect-chassis");
-        our_chassis = chassis_id && !strcmp(chassis_id, chassis_rec->name);
-        if (our_chassis) {
+        if (pb_redirect_chassis_contains(binding_rec, chassis_rec)) {
             add_local_datapath(ldatapaths, lports, binding_rec->datapath,
                                false, local_datapaths);
+            // XXX this should only be set to true if our chassis
+            // (chassis_rec) is the master for this chassisredirect port
+            // but for now we'll bind it only when not bound
+            our_chassis = !binding_rec->chassis;
         }
     } else if (!strcmp(binding_rec->type, "l3gateway")) {
         const char *chassis_id = smap_get(&binding_rec->options,
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
index b1b4b23..b7d1bcb 100644
--- a/ovn/controller/lflow.c
+++ b/ovn/controller/lflow.c
@@ -96,7 +96,11 @@  is_chassis_resident_cb(const void *c_aux_, const char *port_name)
 
     const struct sbrec_port_binding *pb
         = lport_lookup_by_name(c_aux->lports, port_name);
-    return pb && pb->chassis && pb->chassis == c_aux->chassis;
+    if (pb && pb->chassis && pb->chassis == c_aux->chassis) {
+        return true;
+    } else {
+        return pb_redirect_chassis_contains(pb, c_aux->chassis);
+    }
 }
 
 static bool
diff --git a/ovn/controller/lport.c b/ovn/controller/lport.c
index 906fda2..52608a1 100644
--- a/ovn/controller/lport.c
+++ b/ovn/controller/lport.c
@@ -237,3 +237,122 @@  mcgroup_lookup_by_dp_name(const struct mcgroup_index *mcgroups,
     }
     return NULL;
 }
+
+
+/* redirect-chassis option parsing
+ */
+static int
+compare_chassis_prio_(const void *a_, const void *b_)
+{
+    const struct redirect_chassis *chassis_a = a_;
+    const struct redirect_chassis *chassis_b = b_;
+    int prio_diff = chassis_b->prio - chassis_a->prio;
+    if (!prio_diff) {
+        return strcmp(chassis_a->chassis_id, chassis_b->chassis_id);
+    }
+    return prio_diff;
+}
+
+struct ovs_list*
+parse_redirect_chassis(const struct sbrec_port_binding *binding)
+{
+
+    const char *redir_chassis_const;
+    char *redir_chassis_str;
+    char *save_ptr1 = NULL;
+    char *chassis_prio;
+
+    struct redirect_chassis *redirect_chassis =
+        xmalloc(sizeof *redirect_chassis);
+
+    int n=0;
+
+    redir_chassis_const = smap_get(&binding->options, "redirect-chassis");
+
+    if (!redir_chassis_const) {
+        free(redirect_chassis);
+        return NULL;
+    }
+
+    redir_chassis_str = xstrdup(redir_chassis_const);
+
+    for (chassis_prio = strtok_r(redir_chassis_str, ", ", &save_ptr1);
+         chassis_prio; chassis_prio = strtok_r(NULL, ", ", &save_ptr1)) {
+
+        char *save_ptr2 = NULL;
+        char *chassis_name = strtok_r(chassis_prio, ":", &save_ptr2);
+        char *prio = strtok_r(NULL, ":", &save_ptr2);
+
+        if (strlen(chassis_name) > UUID_LEN) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_WARN_RL(&rl, "chassis name (%s) in redirect-chassis option "
+                              "of logical port %s is too long, ignoring.",
+                              chassis_name, binding->logical_port);
+            continue;
+        }
+
+        ovs_strzcpy(redirect_chassis[n].chassis_id, chassis_name,
+                    UUID_LEN + 1);
+
+        /* chassis with no priority get lowest priority: 0 */
+        redirect_chassis[n].prio = prio ? atoi(prio):0;
+
+        redirect_chassis = xrealloc(redirect_chassis,
+                                    sizeof *redirect_chassis * (++n + 1));
+
+    }
+
+    free(redir_chassis_str);
+
+    qsort(redirect_chassis, n, sizeof *redirect_chassis,
+          compare_chassis_prio_);
+
+    struct ovs_list *list = NULL;
+    if (n) {
+        list = xmalloc(sizeof *list);
+        ovs_list_init(list);
+
+        int i;
+        for (i=0; i<n; i++) {
+            ovs_list_push_back(list, &redirect_chassis[i].node);
+        }
+    }
+
+    return list;
+}
+
+bool
+redirect_chassis_contains(const struct ovs_list *redirect_chassis,
+                          const struct sbrec_chassis *chassis)
+{
+    struct redirect_chassis *chassis_item;
+    if (redirect_chassis) {
+        LIST_FOR_EACH (chassis_item, node, redirect_chassis) {
+            if (!strcmp(chassis_item->chassis_id, chassis->name)) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+void
+redirect_chassis_destroy(struct ovs_list *list)
+{
+    if (!list) {
+        return;
+    }
+    free(ovs_list_front(list));
+    free(list);
+}
+
+bool
+pb_redirect_chassis_contains(const struct sbrec_port_binding *binding,
+                             const struct sbrec_chassis *chassis)
+{
+    bool contained;
+    struct ovs_list *redirect_chassis = parse_redirect_chassis(binding);
+    contained = redirect_chassis_contains(redirect_chassis, chassis);
+    redirect_chassis_destroy(redirect_chassis);
+    return contained;
+}
diff --git a/ovn/controller/lport.h b/ovn/controller/lport.h
index fe0e430..329a71f 100644
--- a/ovn/controller/lport.h
+++ b/ovn/controller/lport.h
@@ -17,10 +17,14 @@ 
 #define OVN_LPORT_H 1
 
 #include <stdint.h>
+#include "lib/uuid.h"
 #include "openvswitch/hmap.h"
+#include "openvswitch/list.h"
 
 struct ovsdb_idl;
+struct sbrec_chassis;
 struct sbrec_datapath_binding;
+struct sbrec_port_binding;
 
 /* Database indexes.
  * =================
@@ -93,4 +97,28 @@  const struct sbrec_multicast_group *mcgroup_lookup_by_dp_name(
     const struct sbrec_datapath_binding *,
     const char *name);
 
+
+/* Redirect chassis parsing
+ * ========================
+ *
+ * The following structure and methods allow parsing the redirect-chassis
+ * option on chassisredirect ports. A parsed list will always be in order
+ * of priority. */
+
+struct redirect_chassis {
+    struct ovs_list node;
+    char chassis_id[UUID_LEN+1];
+    int prio;
+};
+
+struct ovs_list*
+parse_redirect_chassis(const struct sbrec_port_binding *binding);
+bool redirect_chassis_contains(const struct ovs_list *redirect_chassis,
+                               const struct sbrec_chassis *chassis);
+void redirect_chassis_destroy(struct ovs_list *list);
+
+bool pb_redirect_chassis_contains(
+        const struct sbrec_port_binding *binding,
+        const struct sbrec_chassis *chassis);
+
 #endif /* ovn/lport.h */
diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c
index 8fbe356..21893bc 100644
--- a/ovn/controller/ovn-controller.c
+++ b/ovn/controller/ovn-controller.c
@@ -148,6 +148,7 @@  update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
     struct ovsdb_idl_condition mg = OVSDB_IDL_CONDITION_INIT(&mg);
     struct ovsdb_idl_condition dns = OVSDB_IDL_CONDITION_INIT(&dns);
     sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "patch");
+    sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "chassisredirect");
     if (chassis) {
         /* This should be mostly redundant with the other clauses for port
          * bindings, but it allows us to catch any ports that are assigned to
@@ -165,10 +166,6 @@  update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
         sbrec_port_binding_add_clause_options(&pb, OVSDB_F_INCLUDES, &l2);
         const struct smap l3 = SMAP_CONST1(&l3, "l3gateway-chassis", id);
         sbrec_port_binding_add_clause_options(&pb, OVSDB_F_INCLUDES, &l3);
-        const struct smap redirect = SMAP_CONST1(&redirect,
-                                                 "redirect-chassis", id);
-        sbrec_port_binding_add_clause_options(&pb, OVSDB_F_INCLUDES,
-                                              &redirect);
     }
     if (local_ifaces) {
         const char *name;
diff --git a/ovn/controller/physical.c b/ovn/controller/physical.c
index f2d9676..1c0ff8f 100644
--- a/ovn/controller/physical.c
+++ b/ovn/controller/physical.c
@@ -19,8 +19,11 @@ 
 #include "flow.h"
 #include "lflow.h"
 #include "lport.h"
+#include "lib/bundle.h"
 #include "lib/poll-loop.h"
+#include "lib/uuid.h"
 #include "ofctrl.h"
+#include "openvswitch/list.h"
 #include "openvswitch/hmap.h"
 #include "openvswitch/match.h"
 #include "openvswitch/ofp-actions.h"
@@ -353,8 +356,12 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
         return;
     }
 
+    struct ovs_list *redirect_chassis = NULL;
+    redirect_chassis = parse_redirect_chassis(binding);
+
     if (!strcmp(binding->type, "chassisredirect")
-        && binding->chassis == chassis) {
+        && (binding->chassis == chassis ||
+            redirect_chassis_contains(redirect_chassis, chassis))) {
 
         /* Table 33, priority 100.
          * =======================
@@ -413,7 +420,8 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
 
         ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0,
                         &match, ofpacts_p);
-        return;
+
+        goto out;
     }
 
     /* Find the OpenFlow port for the logical port, as 'ofport'.  This is
@@ -442,7 +450,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
     bool is_remote = false;
     if (binding->parent_port && *binding->parent_port) {
         if (!binding->tag) {
-            return;
+            goto out;
         }
         ofport = u16_to_ofp(simap_get(&localvif_to_ofport,
                                       binding->parent_port));
@@ -460,27 +468,34 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
         }
     }
 
+    bool is_ha_remote = false;
     const struct chassis_tunnel *tun = NULL;
     const struct sbrec_port_binding *localnet_port =
         get_localnet_port(local_datapaths, dp_key);
     if (!ofport) {
         /* It is remote port, may be reached by tunnel or localnet port */
         is_remote = true;
-        if (!binding->chassis) {
-            return;
-        }
         if (localnet_port) {
             ofport = u16_to_ofp(simap_get(&localvif_to_ofport,
                                           localnet_port->logical_port));
             if (!ofport) {
-                return;
+                goto out;
             }
         } else {
-            tun = chassis_tunnel_find(binding->chassis->name);
-            if (!tun) {
-                return;
+            if (!redirect_chassis || ovs_list_is_short(redirect_chassis)) {
+                /* It's on a single remote chassis */
+                if (!binding->chassis) {
+                    goto out;
+                }
+                tun = chassis_tunnel_find(binding->chassis->name);
+                if (!tun) {
+                    goto out;
+                }
+                ofport = tun->ofport;
+            } else {
+                /* It's distributed across the "redirect_chassis" list */
+                is_ha_remote = true;
             }
-            ofport = tun->ofport;
         }
     }
 
@@ -575,7 +590,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
         }
         ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100, 0,
                         &match, ofpacts_p);
-    } else if (!tun) {
+    } else if (!tun && !is_ha_remote) {
         /* Remote port connected by localnet port */
         /* Table 33, priority 100.
          * =======================
@@ -598,7 +613,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
         put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_p);
         ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0,
                         &match, ofpacts_p);
-    } else {
+    } else  {
         /* Remote port connected by tunnel */
 
         /* Table 32, priority 100.
@@ -615,14 +630,79 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
         match_set_metadata(&match, htonll(dp_key));
         match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
 
-        put_encapsulation(mff_ovn_geneve, tun, binding->datapath,
-                          port_key, ofpacts_p);
+        if (!is_ha_remote) {
+            /* Setup encapsulation */
+            put_encapsulation(mff_ovn_geneve, tun, binding->datapath,
+                              port_key, ofpacts_p);
+            /* Output to tunnel. */
+            ofpact_put_OUTPUT(ofpacts_p)->port = ofport;
+        } else {
+            struct redirect_chassis *chassis;
+            /* Make sure all tunnel endpoints use the same encapsulation,
+             * and set it up */
+            LIST_FOR_EACH (chassis, node, redirect_chassis) {
+                if (!tun) {
+                    tun = chassis_tunnel_find(chassis->chassis_id);
+                } else {
+                    struct chassis_tunnel *chassis_tunnel;
+                    chassis_tunnel = chassis_tunnel_find(chassis->chassis_id);
+                    if (chassis_tunnel &&
+                        tun->type != chassis_tunnel->type) {
+                        static struct vlog_rate_limit rl =
+                            VLOG_RATE_LIMIT_INIT(1, 1);
+                        VLOG_ERR_RL(&rl, "Port %s has redirect-chassis with "
+                                         "mixed encapsulations, only uniform "
+                                         "encapsulations are supported.",
+                                    binding->logical_port);
+                        goto out;
+                    }
+                }
+            }
+            if (!tun) {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1,1);
+                VLOG_ERR_RL(&rl, "No tunnel endpoint found for gateways in "
+                                 "redirect-chassis of port %s",
+                            binding->logical_port);
+                goto out;
+            }
+
+            put_encapsulation(mff_ovn_geneve, tun, binding->datapath,
+                              port_key, ofpacts_p);
 
-        /* Output to tunnel. */
-        ofpact_put_OUTPUT(ofpacts_p)->port = ofport;
+            /* Output to tunnels with active/backup */
+            struct ofpact_bundle *bundle = ofpact_put_BUNDLE(ofpacts_p);
+
+            LIST_FOR_EACH (chassis, node, redirect_chassis) {
+                tun = chassis_tunnel_find(chassis->chassis_id);
+                if (!tun) {
+                    continue;
+                }
+                if (bundle->n_slaves >= BUNDLE_MAX_SLAVES) {
+                    static struct vlog_rate_limit rl =
+                            VLOG_RATE_LIMIT_INIT(1, 1);
+                    VLOG_WARN_RL(&rl, "Remote endpoints for port beyond "
+                                      "BUNDLE_MAX_SLAVES");
+                    break;
+                }
+                ofpbuf_put(ofpacts_p, &tun->ofport,
+                           sizeof tun->ofport);
+                bundle->n_slaves++;
+            }
+
+            ofpact_finish_BUNDLE(ofpacts_p, &bundle);
+            bundle->algorithm = NX_BD_ALG_ACTIVE_BACKUP;
+            /* Although ACTIVE_BACKUP bundle algorithm seems to ignore
+             * the next two fields, those are always set */
+            bundle->basis = 0;
+            bundle->fields = NX_HASH_FIELDS_ETH_SRC;
+        }
         ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100, 0,
                         &match, ofpacts_p);
     }
+out:
+    if (redirect_chassis) {
+        redirect_chassis_destroy(redirect_chassis);
+    }
 }
 
 static void
diff --git a/tests/ovn.at b/tests/ovn.at
index efcbd91..f6a794c 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -7496,3 +7496,117 @@  done
 OVN_CLEANUP([hv1],[hv2])
 
 AT_CLEANUP
+
+AT_SETUP([ovn -- 1 LR with HA distributed router gateway port])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+net_add n1
+
+# create gateways with external network connectivity
+
+for i in 1 2; do
+    sim_add gw$i
+    as gw$i
+    ovs-vsctl add-br br-phys
+    ovn_attach n1 br-phys 192.168.0.$i
+    ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+done
+
+ovn-nbctl ls-add inside
+ovn-nbctl ls-add outside
+
+# create hypervisors with a vif port each to an internal network
+
+for i in 1 2; do
+    sim_add hv$i
+    as hv$i
+    ovs-vsctl add-br br-phys
+    ovn_attach n1 br-phys 192.168.0.1$i
+    ovs-vsctl -- add-port br-int hv$i-vif1 -- \
+        set interface hv$i-vif1 external-ids:iface-id=inside$i \
+        options:tx_pcap=hv$i/vif1-tx.pcap \
+        options:rxq_pcap=hv$i/vif1-rx.pcap \
+        ofport-request=1
+
+        ovn-nbctl lsp-add inside inside$i \
+            -- lsp-set-addresses inside$i "f0:00:00:01:22:$i 192.168.1.10$i"
+
+done
+
+ovn_populate_arp
+
+ovn-nbctl create Logical_Router name=R1
+
+# Connect inside to R1
+ovn-nbctl lrp-add R1 inside 00:00:01:01:02:03 192.168.1.1/24
+ovn-nbctl lsp-add inside rp-inside -- set Logical_Switch_Port rp-inside \
+    type=router options:router-port=inside \
+    -- lsp-set-addresses rp-inside router
+
+# Connect outside to R1 as distributed router gateway port on gw1+gw2
+ovn-nbctl lrp-add R1 outside 00:00:02:01:02:04 192.168.0.101/24 \
+    -- set Logical_Router_Port outside options:redirect-chassis="gw1:10,gw2:1"
+
+ovn-nbctl lsp-add outside rp-outside -- set Logical_Switch_Port rp-outside \
+    type=router options:router-port=outside \
+    -- lsp-set-addresses rp-outside router
+
+# Create localnet port in outside
+ovn-nbctl lsp-add outside ln-outside
+ovn-nbctl lsp-set-addresses ln-outside unknown
+ovn-nbctl lsp-set-type ln-outside localnet
+ovn-nbctl lsp-set-options ln-outside network_name=phys
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+# XXX This should be more systematic.
+sleep 2
+
+echo "---------NB dump-----"
+ovn-nbctl show
+echo "---------------------"
+ovn-nbctl list logical_router
+echo "---------------------"
+ovn-nbctl list logical_router_port
+echo "---------------------"
+
+echo "---------SB dump-----"
+ovn-sbctl list datapath_binding
+echo "---------------------"
+ovn-sbctl list port_binding
+echo "---------------------"
+ovn-sbctl dump-flows
+echo "---------------------"
+ovn-sbctl list chassis
+ovn-sbctl list encap
+echo "---------------------"
+
+
+hv1_gw1_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw1-0)
+hv1_gw2_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw2-0)
+hv2_gw1_ofport=$(as hv2 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw1-0)
+hv2_gw2_ofport=$(as hv2 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw2-0)
+
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=32 | grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport | wc -l], [0], [1
+])
+
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=32 | grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport | wc -l], [0], [1
+])
+
+
+ovn-nbctl set Logical_Router_Port outside options:redirect-chassis="gw1:1,gw2:10"
+
+# XXX: Let the change propagate down to the ovn-controllers
+sleep 2
+
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=32 | grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport | wc -l], [0], [1
+])
+
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=32 | grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport | wc -l], [0], [1
+])
+
+
+OVN_CLEANUP([gw1],[gw2],[hv1],[hv2])
+
+AT_CLEANUP
+