During VM live migration, a CMS needs to know when the destination
chassis has finished installing OpenFlow flows before it can safely
start the VM.  Currently, ovn-controller sets ovn-installed on the
local OVS interface, but this information is not reflected back to
the Southbound DB, requiring a per-chassis agent to monitor readiness.
Add a new Port_Binding options:additional-chassis-ready key that
contains a comma-separated list of chassis names that have completed
flow installation as additional chassis.  ovn-controller sets this
when local_binding_set_up() is called for an additional chassis
binding, and clears it when the chassis is released.
The option is preserved by northd during Port_Binding option rebuilds,
gated on requested_additional_chassis being set, so it is automatically
cleaned up when migration completes.
This differs from the existing additional-chassis-activated option
which is traffic-triggered (RARP/GARP/NA via activation strategy).
The new option is flow-installation-triggered and always-on.

Assisted-by: Claude Opus 4.6, Claude Code
Signed-off-by: Lorenzo Bianconi <[email protected]>
---
Changes in v2:
- Added NEWS entry.
- Replaced custom is_chassis_in_list() and remove_chassis_from_list()
  with sset_from_delimited_string()/sset_contains()/
  sset_find_and_delete()/sset_join(), avoiding double-parsing in
  the release path. [Dumitru]
- Renamed LIST_FOR_EACH iterator in local_binding_set_up() to avoid
  confusing reuse of b_lport. [Dumitru]
- Tests: use fetch_column, remove TAG_UNSTABLE, add --wait=hv
  synchronization, convert wait_column to check_column where
  applicable, fix comment style. [Dumitru]
---
 NEWS                 |   2 +
 controller/binding.c |  54 ++++++++++++++--
 northd/northd.c      |   5 ++
 ovn-sb.xml           |  12 ++++
 tests/ovn.at         | 143 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 212 insertions(+), 4 deletions(-)

diff --git a/NEWS b/NEWS
index 748ae30eb..5bb727c8a 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,7 @@
 Post v26.03.0
 -------------
+   - Added Port_Binding options:additional-chassis-ready to report per-chassis
+     flow installation readiness to the Southbound DB during live migration.
    - Added ability to set any "ipsec_*" NB_Global option to configure the
      IPsec backend.
    - Documented missing ovn-nbctl commands: "mirror-rule-add",
diff --git a/controller/binding.c b/controller/binding.c
index de51be823..b14cf020a 100644
--- a/controller/binding.c
+++ b/controller/binding.c
@@ -1031,11 +1031,35 @@ local_binding_set_up(struct shash *local_bindings, 
const char *pb_name,
                                                     ts_now_str);
     }
 
-    if (!sb_readonly && lbinding && b_lport && b_lport->pb->n_up &&
-            !b_lport->pb->up[0] && b_lport->pb->chassis == chassis_rec) {
-        binding_lport_set_up(b_lport, sb_readonly);
-        LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) {
+    if (!sb_readonly && lbinding && b_lport) {
+        if (b_lport->pb->n_up && !b_lport->pb->up[0] &&
+            b_lport->pb->chassis == chassis_rec) {
             binding_lport_set_up(b_lport, sb_readonly);
+            struct binding_lport *iter;
+            LIST_FOR_EACH (iter, list_node, &lbinding->binding_lports) {
+                binding_lport_set_up(iter, sb_readonly);
+            }
+        }
+
+        if (is_additional_chassis(b_lport->pb, chassis_rec)) {
+            const char *current = smap_get(&b_lport->pb->options,
+                                           "additional-chassis-ready");
+            if (!current) {
+                sbrec_port_binding_update_options_setkey(
+                        b_lport->pb, "additional-chassis-ready",
+                        chassis_rec->name);
+            } else {
+                struct sset ready_set;
+                sset_from_delimited_string(&ready_set, current, ",");
+                if (!sset_contains(&ready_set, chassis_rec->name)) {
+                    char *val = xasprintf("%s,%s", current,
+                                          chassis_rec->name);
+                    sbrec_port_binding_update_options_setkey(
+                            b_lport->pb, "additional-chassis-ready", val);
+                    free(val);
+                }
+                sset_destroy(&ready_set);
+            }
         }
     }
 }
@@ -1570,6 +1594,28 @@ release_lport_additional_chassis(const struct 
sbrec_port_binding *pb,
         remove_additional_chassis(pb, chassis_rec);
     }
 
+    const char *ready = smap_get(&pb->options, "additional-chassis-ready");
+    if (ready) {
+        struct sset ready_set;
+        sset_from_delimited_string(&ready_set, ready, ",");
+        if (sset_find_and_delete(&ready_set, chassis_rec->name)) {
+            if (sb_readonly) {
+                sset_destroy(&ready_set);
+                return false;
+            }
+            if (!sset_is_empty(&ready_set)) {
+                char *updated = sset_join(&ready_set, ",", "");
+                sbrec_port_binding_update_options_setkey(
+                    pb, "additional-chassis-ready", updated);
+                free(updated);
+            } else {
+                sbrec_port_binding_update_options_delkey(
+                    pb, "additional-chassis-ready");
+            }
+        }
+        sset_destroy(&ready_set);
+    }
+
     VLOG_INFO("Releasing lport %s from this additional chassis.",
               pb->logical_port);
     return true;
diff --git a/northd/northd.c b/northd/northd.c
index 0dbf17426..71b3ca9c1 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -2871,6 +2871,11 @@ ovn_port_update_sbrec(struct ovsdb_idl_txn *ovnsb_txn,
                     smap_add(&options, "additional-chassis-activated",
                              activated_str);
                 }
+                const char *ready_str = smap_get(&op->sb->options,
+                                                 "additional-chassis-ready");
+                if (ready_str) {
+                    smap_add(&options, "additional-chassis-ready", ready_str);
+                }
             }
 
             /* Preserve virtual port options. */
diff --git a/ovn-sb.xml b/ovn-sb.xml
index e45b63d73..5175c523a 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -3855,6 +3855,18 @@ tcp.flags = RST;
         that the port was activated using the strategy specified.
       </column>
 
+      <column name="options" key="additional-chassis-ready">
+        A comma-separated list of chassis names that have finished installing
+        OpenFlow flows for this port binding as an additional chassis.  Set by
+        <code>ovn-controller</code> when the interface reaches the
+        <code>ovn-installed</code> state on the additional chassis.  This
+        allows a CMS to monitor the Southbound DB for migration readiness
+        without requiring an agent on each chassis.  The option is
+        automatically cleaned up when the chassis is removed from
+        <ref column="additional_chassis"/> or when
+        <ref column="requested_additional_chassis"/> is cleared.
+      </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/tests/ovn.at b/tests/ovn.at
index 522c1c90d..c19227e98 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -17336,6 +17336,149 @@ OVN_CLEANUP([hv1],[hv2])
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([options:additional-chassis-ready for logical port])
+AT_KEYWORDS([multi-chassis])
+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 --wait=hv 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
+
+pb_uuid=$(fetch_column Port_Binding _uuid logical_port=lsp0)
+
+# additional-chassis-ready should not be set yet.
+AT_CHECK([ovn-sbctl get Port_Binding $pb_uuid options:additional-chassis-ready 
2>/dev/null], [1], [ignore], [ignore])
+
+# Request port binding at an additional chassis (simulate migration start).
+check ovn-nbctl --wait=hv lsp-set-options lsp0 \
+    requested-chassis=hv1,hv2
+
+check_column "$hv1_uuid" Port_Binding chassis logical_port=lsp0
+check_column "$hv2_uuid" Port_Binding additional_chassis logical_port=lsp0
+check_column "$hv2_uuid" Port_Binding requested_additional_chassis 
logical_port=lsp0
+
+# Verify additional-chassis-ready=hv2 is set in Port_Binding options.
+OVS_WAIT_UNTIL([test xhv2 = x$(ovn-sbctl get Port_Binding $pb_uuid 
options:additional-chassis-ready | tr -d '""')])
+
+# Complete migration: move binding to hv2.
+check ovn-nbctl --wait=hv lsp-set-options lsp0 requested-chassis=hv2
+
+check_column "$hv2_uuid" Port_Binding chassis logical_port=lsp0
+check_column "$hv2_uuid" Port_Binding requested_chassis logical_port=lsp0
+check_column "" Port_Binding additional_chassis logical_port=lsp0
+check_column "" Port_Binding requested_additional_chassis logical_port=lsp0
+
+# Verify additional-chassis-ready is cleared after migration completes.
+OVS_WAIT_UNTIL([test x = x$(ovn-sbctl get Port_Binding $pb_uuid 
options:additional-chassis-ready 2>/dev/null)])
+
+OVN_CLEANUP([hv1
+ignored_dp=ls0],[hv2])
+
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([options:additional-chassis-ready with multiple additional chassis])
+AT_KEYWORDS([multi-chassis])
+ovn_start
+
+net_add n1
+
+for i in 1 2 3; do
+    sim_add hv$i
+    as hv$i
+    check ovs-vsctl add-br br-phys
+    ovn_attach n1 br-phys 192.168.0.1$i
+done
+
+check ovn-nbctl ls-add ls0
+check ovn-nbctl lsp-add ls0 lsp0
+
+# Bind the port to hv1 initially.
+check ovn-nbctl --wait=hv lsp-set-options lsp0 requested-chassis=hv1
+
+for i in 1 2 3; do
+    as hv$i check ovs-vsctl -- add-port br-int lsp0 -- \
+        set Interface lsp0 external-ids:iface-id=lsp0
+done
+
+for i in 1 2 3; do
+    wait_row_count Chassis 1 name=hv$i
+done
+hv1_uuid=$(fetch_column Chassis _uuid name=hv1)
+hv2_uuid=$(fetch_column Chassis _uuid name=hv2)
+hv3_uuid=$(fetch_column Chassis _uuid name=hv3)
+
+wait_column "$hv1_uuid" Port_Binding chassis logical_port=lsp0
+
+pb_uuid=$(fetch_column Port_Binding _uuid logical_port=lsp0)
+
+# Request binding at two additional chassis.
+check ovn-nbctl --wait=hv lsp-set-options lsp0 requested-chassis=hv1,hv2,hv3
+
+check_column "$hv1_uuid" Port_Binding chassis logical_port=lsp0
+check_column "$hv2_uuid $hv3_uuid" Port_Binding additional_chassis 
logical_port=lsp0
+
+# Verify additional-chassis-ready contains both hv2 and hv3.
+OVS_WAIT_UNTIL([
+    ready=$(ovn-sbctl get Port_Binding $pb_uuid 
options:additional-chassis-ready | tr -d '""')
+    echo "$ready" | grep -q hv2 && echo "$ready" | grep -q hv3
+])
+
+# Remove hv3 from additional chassis.
+check ovn-nbctl --wait=hv lsp-set-options lsp0 requested-chassis=hv1,hv2
+
+check_column "$hv2_uuid" Port_Binding additional_chassis logical_port=lsp0
+
+# Verify hv3 is removed from additional-chassis-ready but hv2 remains.
+OVS_WAIT_UNTIL([test xhv2 = x$(ovn-sbctl get Port_Binding $pb_uuid 
options:additional-chassis-ready | tr -d '""')])
+
+# Complete migration.
+check ovn-nbctl --wait=hv lsp-set-options lsp0 requested-chassis=hv2
+
+check_column "$hv2_uuid" Port_Binding chassis logical_port=lsp0
+check_column "" Port_Binding additional_chassis logical_port=lsp0
+
+# Verify additional-chassis-ready is fully cleaned up.
+OVS_WAIT_UNTIL([test x = x$(ovn-sbctl get Port_Binding $pb_uuid 
options:additional-chassis-ready 2>/dev/null)])
+
+OVN_CLEANUP([hv1
+ignored_dp=ls0],[hv2],[hv3
+ignored_dp=ls0])
+
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([options:requested-chassis for logical port])
 ovn_start
-- 
2.54.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to