diff mbox series

[ovs-dev,12/15] Introduce LSP:options:requested-additional-chassis

Message ID 20220215035737.1820679-13-ihrachys@redhat.com
State Superseded, archived
Headers show
Series Support additional-chassis for ports | expand

Checks

Context Check Description
ovsrobot/apply-robot fail apply and check: fail
ovsrobot/github-robot-_Build_and_Test fail github build: failed
ovsrobot/github-robot-_ovn-kubernetes fail github build: failed

Commit Message

Ihar Hrachyshka Feb. 15, 2022, 3:57 a.m. UTC
If used in conjunction with requested-chassis, OVN will attempt to
bind the port at another location in addition to the main chassis.

This is useful in live migration scenarios where it's important to
prepare the environment for workloads to move to, avoiding costly flow
configuration at the moment of the final port binding location change.

The patch mimics behavior of requested-chassis. Corresponding database
fields (pb->additional_chassis, pb->requested_additional_chassis,
pb->additional_encap) are introduced as part of the patch.

Signed-off-by: Ihar Hrachyshka <ihrachys@redhat.com>
---
 controller/binding.c | 178 +++++++++++++++++++++++++++++++++----------
 controller/lport.c   |  19 ++++-
 northd/northd.c      |  64 +++++++++++++---
 northd/ovn-northd.c  |   4 +-
 ovn-nb.xml           |   8 ++
 ovn-sb.ovsschema     |  15 +++-
 ovn-sb.xml           |  58 +++++++++++++-
 tests/ovn.at         |  91 ++++++++++++++++++++++
 8 files changed, 379 insertions(+), 58 deletions(-)

Comments

0-day Robot Feb. 15, 2022, 5:28 a.m. UTC | #1
Bleep bloop.  Greetings Ihar Hrachyshka, I am a robot and I have tried out your patch.
Thanks for your contribution.

I encountered some error that I wasn't expecting.  See the details below.


build:
    -e 's,[@]PKIDIR[@],/usr/local/var/lib/openvswitch/pki,g' \
    -e 's,[@]LOGDIR[@],/usr/local/var/log/ovn,g' \
    -e 's,[@]DBDIR[@],/usr/local/etc/ovn,g' \
    -e 's,[@]PYTHON3[@],/bin/python3,g' \
    -e 's,[@]OVN_RUNDIR[@],/usr/local/var/run/ovn,g' \
    -e 's,[@]OVSBUILDDIR[@],/var/lib/jenkins/jobs/0day_robot_upstream_build_ovn_from_pw/workspace/OVSDIR,g' \
    -e 's,[@]VERSION[@],21.12.90,g' \
    -e 's,[@]OVSVERSION[@],2.17.90,g' \
    -e 's,[@]localstatedir[@],/usr/local/var,g' \
    -e 's,[@]pkgdatadir[@],/usr/local/share/ovn,g' \
    -e 's,[@]sysconfdir[@],/usr/local/etc,g' \
    -e 's,[@]bindir[@],/usr/local/bin,g' \
    -e 's,[@]sbindir[@],/usr/local/sbin,g' \
    -e 's,[@]abs_builddir[@],/var/lib/jenkins/jobs/0day_robot_upstream_build_ovn_from_pw/workspace,g' \
    -e 's,[@]abs_top_srcdir[@],/var/lib/jenkins/jobs/0day_robot_upstream_build_ovn_from_pw/workspace,g' \
  > utilities/ovn-lib.tmp
mv utilities/ovn-lib.tmp utilities/ovn-lib
/bin/sh /var/lib/jenkins/jobs/0day_robot_upstream_build_ovn_from_pw/workspace/build-aux/missing autom4te --language=autotest -I '.' -o tests/testsuite.tmp tests/testsuite.at
mv tests/testsuite.tmp tests/testsuite
touch -c manpage-check
./build-aux/cksum-schema-check ovn-sb.ovsschema ovn-sb.ovsschema.stamp
ovn-sb.ovsschema:4: The checksum "2080576408 27879" was calculated from the schema file and does not match cksum field in the schema file - you should probably update the version number and the checksum in the schema file with the value listed here.
make[1]: *** [ovn-sb.ovsschema.stamp] Error 1
make[1]: Leaving directory `/var/lib/jenkins/jobs/0day_robot_upstream_build_ovn_from_pw/workspace'
make: *** [all] Error 2


Please check this out.  If you feel there has been an error, please email aconole@redhat.com

Thanks,
0-day Robot
diff mbox series

Patch

diff --git a/controller/binding.c b/controller/binding.c
index c7a13d5d5..ec8bff3d8 100644
--- a/controller/binding.c
+++ b/controller/binding.c
@@ -912,6 +912,26 @@  claimed_lport_set_up(const struct sbrec_port_binding *pb,
     }
 }
 
+typedef void (*set_func)(const struct sbrec_port_binding *pb,
+                         const struct sbrec_encap *);
+
+static bool
+update_port_encap_if_needed(const struct sbrec_port_binding *pb,
+                            const struct sbrec_chassis *chassis_rec,
+                            const struct ovsrec_interface *iface_rec,
+                            bool sb_readonly, set_func f)
+{
+    const struct sbrec_encap *encap_rec =
+        sbrec_get_port_encap(chassis_rec, iface_rec);
+    if (encap_rec && pb->encap != encap_rec) {
+        if (sb_readonly) {
+            return false;
+        }
+        f(pb, encap_rec);
+    }
+    return true;
+}
+
 /* Returns false if lport is not claimed due to 'sb_readonly'.
  * Returns true otherwise.
  */
@@ -928,37 +948,68 @@  claim_lport(const struct sbrec_port_binding *pb,
         claimed_lport_set_up(pb, parent_pb, chassis_rec, notify_up, if_mgr);
     }
 
-    if (pb->chassis != chassis_rec) {
-        if (sb_readonly) {
-            return false;
-        }
+    if (!pb->requested_chassis || pb->requested_chassis == chassis_rec) {
+        if (pb->chassis != chassis_rec) {
+            if (sb_readonly) {
+                return false;
+            }
 
-        if (pb->chassis) {
-            VLOG_INFO("Changing chassis for lport %s from %s to %s.",
-                    pb->logical_port, pb->chassis->name,
-                    chassis_rec->name);
-        } else {
-            VLOG_INFO("Claiming lport %s for this chassis.", pb->logical_port);
-        }
-        for (int i = 0; i < pb->n_mac; i++) {
-            VLOG_INFO("%s: Claiming %s", pb->logical_port, pb->mac[i]);
+            if (pb->chassis) {
+                VLOG_INFO("Changing chassis for lport %s from %s to %s.",
+                        pb->logical_port, pb->chassis->name,
+                        chassis_rec->name);
+            } else {
+                VLOG_INFO("Claiming lport %s for this chassis.",
+                          pb->logical_port);
+            }
+            for (int i = 0; i < pb->n_mac; i++) {
+                VLOG_INFO("%s: Claiming %s", pb->logical_port, pb->mac[i]);
+            }
+
+            sbrec_port_binding_set_chassis(pb, chassis_rec);
+            if (pb->additional_chassis == chassis_rec) {
+                sbrec_port_binding_set_additional_chassis(pb, NULL);
+                if (pb->additional_encap) {
+                    sbrec_port_binding_set_additional_encap(pb, NULL);
+                }
+            }
         }
+    } else if (pb->requested_additional_chassis == chassis_rec) {
+        if (pb->additional_chassis != chassis_rec) {
+            if (sb_readonly) {
+                return false;
+            }
 
-        sbrec_port_binding_set_chassis(pb, chassis_rec);
+            if (pb->additional_chassis) {
+                VLOG_INFO(
+                    "Changing additional chassis for lport %s from %s to %s.",
+                    pb->logical_port, pb->chassis->name, chassis_rec->name);
+            } else {
+                VLOG_INFO(
+                    "Claiming lport %s for this additional chassis.",
+                    pb->logical_port);
+            }
+            for (int i = 0; i < pb->n_mac; i++) {
+                VLOG_INFO("%s: Claiming %s", pb->logical_port, pb->mac[i]);
+            }
 
-        if (tracked_datapaths) {
-            update_lport_tracking(pb, tracked_datapaths, true);
+            sbrec_port_binding_set_additional_chassis(pb, chassis_rec);
         }
     }
 
+    if (tracked_datapaths) {
+        update_lport_tracking(pb, tracked_datapaths, true);
+    }
+
     /* Check if the port encap binding, if any, has changed */
-    struct sbrec_encap *encap_rec =
-        sbrec_get_port_encap(chassis_rec, iface_rec);
-    if (encap_rec && pb->encap != encap_rec) {
-        if (sb_readonly) {
-            return false;
-        }
-        sbrec_port_binding_set_encap(pb, encap_rec);
+    if (pb->chassis == chassis_rec) {
+        return update_port_encap_if_needed(
+            pb, chassis_rec, iface_rec, sb_readonly,
+            &sbrec_port_binding_set_encap);
+    } else if (pb->additional_chassis == chassis_rec) {
+        return update_port_encap_if_needed(
+            pb, chassis_rec, iface_rec, sb_readonly,
+            &sbrec_port_binding_set_additional_encap);
     }
 
     return true;
@@ -972,7 +1023,8 @@  claim_lport(const struct sbrec_port_binding *pb,
  * Caller should make sure that this is the case.
  */
 static bool
-release_lport_(const struct sbrec_port_binding *pb, bool sb_readonly)
+release_lport_main_chassis(const struct sbrec_port_binding *pb,
+                           bool sb_readonly)
 {
     if (pb->encap) {
         if (sb_readonly) {
@@ -1000,11 +1052,41 @@  release_lport_(const struct sbrec_port_binding *pb, bool sb_readonly)
 }
 
 static bool
-release_lport(const struct sbrec_port_binding *pb, bool sb_readonly,
+release_lport_additional_chassis(const struct sbrec_port_binding *pb,
+                                 bool sb_readonly)
+{
+    if (pb->additional_encap) {
+        if (sb_readonly) {
+            return false;
+        }
+        sbrec_port_binding_set_additional_encap(pb, NULL);
+    }
+
+    if (pb->additional_chassis) {
+        if (sb_readonly) {
+            return false;
+        }
+        sbrec_port_binding_set_additional_chassis(pb, NULL);
+    }
+
+    VLOG_INFO("Releasing lport %s from this additional chassis.",
+              pb->logical_port);
+    return true;
+}
+
+static bool
+release_lport(const struct sbrec_port_binding *pb,
+              const struct sbrec_chassis *chassis_rec, bool sb_readonly,
               struct hmap *tracked_datapaths, struct if_status_mgr *if_mgr)
 {
-    if (!release_lport_(pb, sb_readonly)) {
-        return false;
+    if (pb->chassis == chassis_rec) {
+        if (!release_lport_main_chassis(pb, sb_readonly)) {
+            return false;
+        }
+    } else if (pb->additional_chassis == chassis_rec) {
+        if (!release_lport_additional_chassis(pb, sb_readonly)) {
+            return false;
+        }
     }
 
     update_lport_tracking(pb, tracked_datapaths, false);
@@ -1023,7 +1105,8 @@  is_binding_lport_this_chassis(struct binding_lport *b_lport,
                               const struct sbrec_chassis *chassis)
 {
     return (b_lport && b_lport->pb && chassis &&
-            b_lport->pb->chassis == chassis);
+            (b_lport->pb->chassis == chassis
+             || b_lport->pb->additional_chassis == chassis));
 }
 
 /* Returns 'true' if the 'lbinding' has binding lports of type LP_CONTAINER,
@@ -1048,7 +1131,7 @@  release_binding_lport(const struct sbrec_chassis *chassis_rec,
 {
     if (is_binding_lport_this_chassis(b_lport, chassis_rec)) {
         remove_related_lport(b_lport->pb, b_ctx_out);
-        if (!release_lport(b_lport->pb, sb_readonly,
+        if (!release_lport(b_lport->pb, chassis_rec, sb_readonly,
                            b_ctx_out->tracked_dp_bindings,
                            b_ctx_out->if_mgr)) {
             return false;
@@ -1097,22 +1180,31 @@  consider_vif_lport_(const struct sbrec_port_binding *pb,
         } else {
             /* We could, but can't claim the lport. */
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+            if (!pb->chassis || pb->chassis == b_ctx_in->chassis_rec) {
+                VLOG_INFO_RL(&rl,
+                    "Not claiming lport %s, chassis %s requested-chassis %s",
+                    pb->logical_port, b_ctx_in->chassis_rec->name,
+                    pb->requested_chassis ?
+                        pb->requested_chassis->name :
+                        "(option points at non-existent chassis)");
+            } else {
                 VLOG_INFO_RL(&rl,
-                             "Not claiming lport %s, chassis %s "
-                             "requested-chassis %s",
-                             pb->logical_port,
-                             b_ctx_in->chassis_rec->name,
-                             pb->requested_chassis ?
-                             pb->requested_chassis->name : "(option points at "
-                                                           "non-existent "
-                                                           "chassis)");
+                    "Not claiming lport %s, chassis %s "
+                    "requested-additional-chassis %s",
+                    pb->logical_port, b_ctx_in->chassis_rec->name,
+                    pb->requested_additional_chassis ?
+                        pb->requested_additional_chassis->name :
+                        "(option points at non-existent chassis)");
+            }
         }
     }
 
-    if (pb->chassis == b_ctx_in->chassis_rec) {
+    if (pb->chassis == b_ctx_in->chassis_rec
+            || pb->additional_chassis == b_ctx_in->chassis_rec) {
         /* Release the lport if there is no lbinding. */
         if (!lbinding_set || !can_bind) {
-            return release_lport(pb, !b_ctx_in->ovnsb_idl_txn,
+            return release_lport(pb, b_ctx_in->chassis_rec,
+                                 !b_ctx_in->ovnsb_idl_txn,
                                  b_ctx_out->tracked_dp_bindings,
                                  b_ctx_out->if_mgr);
         }
@@ -1234,7 +1326,8 @@  consider_container_lport(const struct sbrec_port_binding *pb,
          * 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,
+            return release_lport(pb, b_ctx_in->chassis_rec,
+                                 !b_ctx_in->ovnsb_idl_txn,
                                  b_ctx_out->tracked_dp_bindings,
                                  b_ctx_out->if_mgr);
         }
@@ -1328,7 +1421,7 @@  consider_localport(const struct sbrec_port_binding *pb,
     /* If the port binding is claimed, then release it as localport is claimed
      * by any ovn-controller. */
     if (pb->chassis == b_ctx_in->chassis_rec) {
-        if (!release_lport_(pb, !b_ctx_in->ovnsb_idl_txn)) {
+        if (!release_lport_main_chassis(pb, !b_ctx_in->ovnsb_idl_txn)) {
             return false;
         }
 
@@ -1363,7 +1456,8 @@  consider_nonvif_lport_(const struct sbrec_port_binding *pb,
                            b_ctx_out->tracked_dp_bindings,
                            b_ctx_out->if_mgr);
     } else if (pb->chassis == b_ctx_in->chassis_rec) {
-        return release_lport(pb, !b_ctx_in->ovnsb_idl_txn,
+        return release_lport(pb, b_ctx_in->chassis_rec,
+                             !b_ctx_in->ovnsb_idl_txn,
                              b_ctx_out->tracked_dp_bindings,
                              b_ctx_out->if_mgr);
     }
diff --git a/controller/lport.c b/controller/lport.c
index 5ad40f6d3..feab18cf8 100644
--- a/controller/lport.c
+++ b/controller/lport.c
@@ -129,8 +129,23 @@  lport_can_bind_on_this_chassis(const struct sbrec_chassis *chassis_rec,
         return !strcmp(requested_chassis_option, chassis_rec->name)
                || !strcmp(requested_chassis_option, chassis_rec->hostname);
     }
-    return !requested_chassis_option || !requested_chassis_option[0]
-           || chassis_rec == pb->requested_chassis;
+    if (!requested_chassis_option || !requested_chassis_option[0]
+           || chassis_rec == pb->requested_chassis) {
+        return true;
+    }
+
+    const char *rac_option = smap_get(&pb->options,
+                                      "requested-additional-chassis");
+    if (rac_option && rac_option[0]) {
+        if (pb->requested_additional_chassis) {
+            return (pb->requested_chassis &&
+                    chassis_rec == pb->requested_additional_chassis);
+        } else {
+            return !strcmp(rac_option, chassis_rec->name)
+                   || !strcmp(rac_option, chassis_rec->hostname);
+        }
+    }
+    return false;
 }
 
 const struct sbrec_datapath_binding *
diff --git a/northd/northd.c b/northd/northd.c
index 0179b2bc9..108ccb14f 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -3139,17 +3139,17 @@  ovn_port_update_sbrec_chassis(
         const struct ovn_port *op)
 {
     const char *requested_chassis; /* May be NULL. */
-    bool reset_requested_chassis = false;
+    const char *requested_additional_chassis; /* May be NULL. */
+    const struct sbrec_chassis *requested_chassis_sb = NULL;
+    const struct sbrec_chassis *requested_additional_chassis_sb = NULL;
+
     requested_chassis = smap_get(&op->nbsp->options,
                                  "requested-chassis");
     if (requested_chassis) {
-        const struct sbrec_chassis *chassis = chassis_lookup(
+        requested_chassis_sb = chassis_lookup(
             sbrec_chassis_by_name, sbrec_chassis_by_hostname,
             requested_chassis);
-        if (chassis) {
-            sbrec_port_binding_set_requested_chassis(op->sb, chassis);
-        } else {
-            reset_requested_chassis = true;
+        if (!requested_chassis_sb) {
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(
                 1, 1);
             VLOG_WARN_RL(
@@ -3158,12 +3158,58 @@  ovn_port_update_sbrec_chassis(
                 "options:requested-chassis on LSP '%s'.",
                 requested_chassis, op->nbsp->name);
         }
-    } else if (op->sb->requested_chassis) {
-        reset_requested_chassis = true;
     }
-    if (reset_requested_chassis) {
+
+    requested_additional_chassis = smap_get(
+        &op->nbsp->options, "requested-additional-chassis");
+    if (requested_additional_chassis) {
+        requested_additional_chassis_sb = chassis_lookup(
+            sbrec_chassis_by_name, sbrec_chassis_by_hostname,
+            requested_additional_chassis);
+        if (!requested_additional_chassis_sb) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(
+                1, 1);
+            VLOG_WARN_RL(
+                &rl,
+                "Unknown chassis '%s' set as "
+                "options:requested-additional-chassis on LSP '%s'.",
+                requested_additional_chassis, op->nbsp->name);
+        } else if (requested_chassis_sb == requested_additional_chassis_sb) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(
+                1, 1);
+            VLOG_WARN_RL(
+                &rl,
+                "options:requested-chassis (%s) and "
+                "options:requested-additional-chassis (%s) point to the same "
+                "chassis on LSP '%s'. Ignoring additional chassis.",
+                requested_chassis, requested_additional_chassis,
+                op->nbsp->name);
+            requested_additional_chassis_sb = NULL;
+        } else if (!requested_chassis_sb) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(
+                1, 1);
+            VLOG_WARN_RL(
+                &rl,
+                "Chassis '%s' set as "
+                "options:requested-additional-chassis on LSP '%s', but "
+                "options:requested-chassis is unset. "
+                "Ignoring additional chassis.",
+                requested_additional_chassis, op->nbsp->name);
+            requested_additional_chassis_sb = NULL;
+        }
+    }
+
+    if (requested_chassis_sb) {
+        sbrec_port_binding_set_requested_chassis(op->sb, requested_chassis_sb);
+    } else if (op->sb->requested_chassis) {
         sbrec_port_binding_set_requested_chassis(op->sb, NULL);
     }
+    if (requested_additional_chassis_sb) {
+        sbrec_port_binding_set_requested_additional_chassis(
+            op->sb, requested_additional_chassis_sb);
+    } else if (op->sb->requested_additional_chassis) {
+        sbrec_port_binding_set_requested_additional_chassis(op->sb, NULL);
+    }
 }
 
 static void
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 8a8c6d07d..984105a5f 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -100,7 +100,9 @@  static const char *rbac_fdb_update[] =
 static const char *rbac_port_binding_auth[] =
     {""};
 static const char *rbac_port_binding_update[] =
-    {"chassis", "encap", "up", "virtual_parent"};
+    {"chassis", "additional_chassis",
+     "encap", "additional_encap",
+     "up", "virtual_parent"};
 
 static const char *rbac_mac_binding_auth[] =
     {""};
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 6a6972856..2ec3b90aa 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1005,6 +1005,14 @@ 
           one chassis.
         </column>
 
+        <column name="options" key="requested-additional-chassis">
+          If set and used with <ref column="requested-chassis"/>, identifies an
+          additional chassis (by name or hostname) that is allowed to bind this
+          port. Using this option allows to configure a new location for a port
+          binding before the old location is ripped off. This option may be
+          useful in live migration scenarios, for port mirroring, etc.
+        </column>
+
         <column name="options" key="iface-id-ver">
           If set, this port will be bound by <code>ovn-controller</code>
           only if this same key and value is configured in the
diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
index 122614dd5..175c918a1 100644
--- a/ovn-sb.ovsschema
+++ b/ovn-sb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Southbound",
     "version": "20.21.0",
-    "cksum": "2362446865 26963",
+    "cksum": "1635579573 27862",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -218,10 +218,18 @@ 
                                              "refTable": "Chassis",
                                              "refType": "weak"},
                                      "min": 0, "max": 1}},
+                "additional_chassis": {"type": {"key": {"type": "uuid",
+                                                        "refTable": "Chassis",
+                                                        "refType": "weak"},
+                                               "min": 0, "max": 1}},
                 "encap": {"type": {"key": {"type": "uuid",
                                             "refTable": "Encap",
                                              "refType": "weak"},
                                     "min": 0, "max": 1}},
+                "additional_encap": {"type": {"key": {"type": "uuid",
+                                                      "refTable": "Encap",
+                                                      "refType": "weak"},
+                                    "min": 0, "max": 1}},
                 "mac": {"type": {"key": "string",
                                  "min": 0,
                                  "max": "unlimited"}},
@@ -236,6 +244,11 @@ 
                 "requested_chassis": {"type": {"key": {"type": "uuid",
                                                        "refTable": "Chassis",
                                                        "refType": "weak"},
+                                               "min": 0, "max": 1}},
+                "requested_additional_chassis": {
+                                      "type": {"key": {"type": "uuid",
+                                                       "refTable": "Chassis",
+                                                       "refType": "weak"},
                                                "min": 0, "max": 1}}},
             "indexes": [["datapath", "tunnel_key"], ["logical_port"]],
             "isRoot": true},
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 9ddacdf09..f43bb6804 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -2835,9 +2835,17 @@  tcp.flags = RST;
       </column>
 
       <column name="encap">
-        Points to supported encapsulation configurations to transmit
-        logical dataplane packets to this chassis.  Each entry is a <ref
-        table="Encap"/> record that describes the configuration.
+        Points to preferred encapsulation configuration to transmit
+        logical dataplane packets to this chassis. The entry is reference to
+        a <ref table="Encap"/> record.
+      </column>
+
+      <column name="additional_encap">
+        Points to preferred encapsulation configuration to transmit
+        logical dataplane packets to this additional chassis. The entry is
+        reference to a <ref table="Encap"/> record.
+
+        See also <ref column="additional_chassis"/>.
       </column>
 
       <column name="chassis">
@@ -2889,6 +2897,13 @@  tcp.flags = RST;
 
       </column>
 
+      <column name="additional_chassis">
+        The meaning of this column is the same as for the
+        <ref column="chassis"/>. The column is used to track an additional
+        physical location of the logical port. Used with regular (empty
+        <ref column="type"/>) port bindings.
+      </column>
+
       <column name="gateway_chassis">
         <p>
           A list of <ref table="Gateway_Chassis"/>.
@@ -3047,6 +3062,33 @@  tcp.flags = RST;
         db="OVN_Northbound"/>
         is defined and contains a string matching the name or hostname of an
         existing chassis.
+
+        See also
+        <ref table="Port_Binding" column="requested_additional_chassis"/>.
+      </column>
+      <column name="requested_additional_chassis">
+        This column exists so that the ovn-controller can effectively monitor
+        all <ref table="Port_Binding"/> records destined for it, and is a
+        supplement to the <ref
+        table="Port_Binding"
+        column="options"
+        key="requested-additional-chassis"/> option.  The option is still
+        required so that the ovn-controller can check the CMS intent when the
+        chassis pointed to does not currently exist, which for example occurs
+        when the ovn-controller is stopped without passing the --restart
+        argument.
+
+        This column must be a
+        <ref table="Chassis"/> record.  This is populated by
+        <code>ovn-northd</code> when the <ref
+        table="Logical_Switch_Port"
+        column="options"
+        key="requested-additional-chassis"
+        db="OVN_Northbound"/>
+        is defined and contains a string matching the name or hostname of an
+        existing chassis.
+
+        See also <ref table="Port_Binding" column="requested_chassis"/>.
       </column>
     </group>
 
@@ -3212,6 +3254,16 @@  tcp.flags = RST;
         a live migration. It can also prevent similar thrashing due to a
         mis-configuration, if a port is accidentally created on more than
         one chassis.
+
+        See also <ref column="options" key="additional-chassis"/>.
+      </column>
+
+      <column name="options" key="requested-additional-chassis">
+        If set and used with <ref column="requested-chassis"/>, identifies an
+        additional chassis (by name or hostname) that is allowed to bind this
+        port. Using this option allows to configure a new location for a port
+        binding before the old location is ripped off. This option may be
+        useful in live migration scenarios, for port mirroring, etc.
       </column>
 
       <column name="options" key="iface-id-ver">
diff --git a/tests/ovn.at b/tests/ovn.at
index bbba41af4..3a5973459 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -13597,6 +13597,97 @@  OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([options:requested-additional-chassis for logical port])
+ovn_start
+
+net_add n1
+
+sim_add hv1
+as hv1
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.11
+
+sim_add hv2
+as hv2
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.12
+
+check ovn-nbctl ls-add ls0
+check ovn-nbctl lsp-add ls0 lsp0
+
+# Allow only chassis hv1 to bind logical port lsp0.
+check ovn-nbctl lsp-set-options lsp0 requested-chassis=hv1
+
+as hv1 check ovs-vsctl -- add-port br-int lsp0 -- \
+    set Interface lsp0 external-ids:iface-id=lsp0
+as hv2 check ovs-vsctl -- add-port br-int lsp0 -- \
+    set Interface lsp0 external-ids:iface-id=lsp0
+
+wait_row_count Chassis 1 name=hv1
+wait_row_count Chassis 1 name=hv2
+hv1_uuid=$(fetch_column Chassis _uuid name=hv1)
+hv2_uuid=$(fetch_column Chassis _uuid name=hv2)
+
+wait_column "$hv1_uuid" Port_Binding chassis logical_port=lsp0
+wait_column "$hv1_uuid" Port_Binding requested_chassis logical_port=lsp0
+wait_column "" Port_Binding additional_chassis logical_port=lsp0
+wait_column "" Port_Binding requested_additional_chassis logical_port=lsp0
+
+# Request port binding at an additional chassis
+check ovn-nbctl lsp-set-options lsp0 \
+    requested-chassis=hv1 \
+    requested-additional-chassis=hv2
+
+wait_column "$hv1_uuid" Port_Binding chassis logical_port=lsp0
+wait_column "$hv1_uuid" Port_Binding requested_chassis logical_port=lsp0
+wait_column "$hv2_uuid" Port_Binding additional_chassis logical_port=lsp0
+wait_column "$hv2_uuid" Port_Binding requested_additional_chassis logical_port=lsp0
+
+# Check that setting iface:encap-ip populates Port_Binding:additional_encap
+wait_row_count Encap 2 chassis_name=hv1
+wait_row_count Encap 2 chassis_name=hv2
+encap_hv1_uuid=$(fetch_column Encap _uuid chassis_name=hv1 type=geneve)
+encap_hv2_uuid=$(fetch_column Encap _uuid chassis_name=hv2 type=geneve)
+
+wait_column "" Port_Binding encap logical_port=lsp0
+wait_column "" Port_Binding additional_encap logical_port=lsp0
+
+as hv1 check ovs-vsctl -- \
+    set Interface lsp0 external-ids:encap-ip=192.168.0.11
+as hv2 check ovs-vsctl -- \
+    set Interface lsp0 external-ids:encap-ip=192.168.0.12
+
+wait_column "$encap_hv1_uuid" Port_Binding encap logical_port=lsp0
+wait_column "$encap_hv2_uuid" Port_Binding additional_encap logical_port=lsp0
+
+# Complete moving the binding to the new location
+check ovn-nbctl lsp-set-options lsp0 requested-chassis=hv2
+
+wait_column "$hv2_uuid" Port_Binding chassis logical_port=lsp0
+wait_column "$hv2_uuid" Port_Binding requested_chassis logical_port=lsp0
+wait_column "" Port_Binding additional_chassis logical_port=lsp0
+wait_column "" Port_Binding requested_additional_chassis logical_port=lsp0
+
+# Check that additional_encap is cleared
+wait_column "" Port_Binding additional_encap logical_port=lsp0
+
+# Check that abrupted port migration clears additional_encap
+check ovn-nbctl lsp-set-options lsp0 \
+    requested-chassis=hv2 \
+    requested-additional-chassis=hv1
+wait_column "$hv2_uuid" Port_Binding chassis logical_port=lsp0
+wait_column "$hv2_uuid" Port_Binding requested_chassis logical_port=lsp0
+wait_column "$hv1_uuid" Port_Binding additional_chassis logical_port=lsp0
+wait_column "$hv1_uuid" Port_Binding requested_additional_chassis logical_port=lsp0
+check ovn-nbctl lsp-set-options lsp0 requested-chassis=hv2
+wait_column "" Port_Binding additional_encap logical_port=lsp0
+
+OVN_CLEANUP([hv1],[hv2])
+
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([options:requested-chassis for logical port])
 ovn_start